Site icon AppTractor

Эффект глюка на Jetpack Compose

Эффекты глитча (глюка, сбоя) — неотъемлемая часть научно-фантастических фильмов и киберпанк-игр. В этой статье мы увидим, как легко реализовать этот эффект в Jetpack Compose.

Это гораздо проще, чем кажется. К тому же, он открывает множество возможностей для творческих интерпретаций.

Графический слой

Поскольку мы будем рисовать несколько копий нашего Composable, нам нужно найти способ сделать это недорого и производительно.

Для этого мы можем использовать graphicsLayer.

val graphicsLayer = rememberGraphicsLayer()

После его создания мы можем «записать» наш контент в модификатор drawWithContent.

modifier = Modifier  
    .drawWithContent {  
        graphicsLayer.record { this@drawWithContent.drawContent() }  
        drawLayer(graphicsLayer)
    }

Эти несколько мощных строк кода рисуют контент во внеэкранный буфер, который затем нужно отобразить вручную с помощью drawLayer. Преимущество такого подхода в том, что мы можем вызывать drawLayer несколько раз — с гораздо меньшими затратами производительности, чем при рендеринге нескольких composable-элементов.

Кроме того, у нас есть все наши стандартные функции холста для управления контентом.

Разрезание

Теперь давайте разрежем наше представление на несколько горизонтальных фрагментов. Позже мы применим случайные перемещения и масштабирование к каждому фрагменту.

graphicsLayer.record { this@drawWithContent.drawContent() }  
for (i in 0 until slices) {  
    clipRect(  
        top = (i / slices.toFloat()) * size.height,  
        bottom = (((i + 1) / slices.toFloat()) * size.height) + 1f,  
    ) {  
        drawLayer(graphicsLayer)  
    }  
}

Здесь мы создаём «срезы», вызывая drawLayer несколько раз, но с разным клиппингом. Позиция и размер каждого фрагмента вычисляются в зависимости от количества срезов.

Я также добавил 1 px к высоте, чтобы избежать странных зазоров между ними.

Добавляем хаос

Здесь вы можете дать волю всевозможным случайным мутациям. В каждом срезе мы добавим случайное смещение (ось X), масштаб (ось X) и случайное наложение цвета.

Но, конечно, вы можете экспериментировать с множеством других вариантов.

translate(  
    left = if (Random.nextInt(5) < step)  
        Random.nextInt(-20..20).toFloat() * intensity  
    else  
        0f  
) {
	// ...
}

Мы применяем эти мутации, обёртывая clipRect другими функциями. Сначала применим смещение вдоль оси X с помощью translate(). Это случайным образом применит смещение в заданном диапазоне.

scale(  
    scaleY = 1f,  
    scaleX = if (Random.nextInt(10) < step)  
        1f + (1f * Random.nextFloat() * intensity)  
    else  
        1f  
) {
	// ...
}

Далее у нас идет scale(), который также масштабирует фрагмент по оси x.

drawLayer(graphicsLayer)  
if (Random.nextInt(5, 30) < step) {  
    drawRect(  
        color = glitchColors.random(),  
        blendMode = BlendMode.SrcAtop  
    )  
}

И наконец, мы можем просто случайным образом нарисовать прямоугольник случайным цветом.

Тайминг и плавность

Подождите-ка! Вы, возможно, спрашиваете себя, что это за step и intensity я использовал. Они нужны для тайминга анимации и придают ей более естественный вид, чем просто чистый хаос.

var step by remember { mutableStateOf(0) }  
  
LaunchedEffect(key) {  
    Animatable(10f)  
        .animateTo(  
            targetValue = 0f,  
            animationSpec = tween(  
                durationMillis = 500,  
                easing = LinearEasing,  
            )  
        ) {  
            step = this.value.roundToInt()  
        }  
}

Переменная step — это целое число, которое анимируется от 10 до 0. Мы используем для этого целое число вместо числа с плавающей точкой, чтобы обеспечить дискретные изменения в нашем эффекте глитча. Всякий раз, когда меняется шаг, в нашем эффекте глитча будут происходить новые мутации.

Но мы также хотим, чтобы наш эффект начинался с хаоса, а затем постепенно затухал по мере его развития. Именно поэтому мы используем step, чтобы определить, когда следует применять мутацию.

left = if (Random.nextInt(5) < step)  
        Random.nextInt(-20..20).toFloat() * intensity  
    else  
        0f

Например, по мере уменьшения шага до 0 количество случаев применения данной мутации будет уменьшаться.

С помощью шага мы также можем получить интенсивность, которая должна применяться к каждой мутации.

val intensity = step / 10f

Применив эти две переменные, мы можем увидеть разницу, поскольку анимация замедляется к концу.

Итак, теперь мы знаем, как создать эффект глюка. Полный код Modifier вы можете найти тут.

Exit mobile version