Jetpack Compose for Desktop을 사용한 게임 구성 팁

Jetpack Compose for Desktop에서 클래식 아케이드 게임Asteroids을 구축한 복제된 블로그 시리즈의 첫 부분에서 우리는 메인 게임의 순환을 어떻게 실현하고 관리 상태와 기본 모양을 그리는지 이해했다.본고에서 우리는 게임이 실현하는 더 많은 세부 사항을 탐색할 것이다.여기에는 다음이 포함됩니다.

  • 렌더링 디테일 - 게임 대상이 게임 영역에서 벗어나지 않도록 하고 장치에 독립된 좌표계를 사용하여 렌더링

  • 기하학과 선형대수-우주선을 비행시키는 비밀소스

  • 프레임 독립 운동 - 우리의 게임을 시종일관 한결같이 하자.
  • 우리 이 화제들을 알아봅시다!

    렌더링:자르기 및 좌표계


    과장된 환경에서 두 가지 측면이 여전히 우리가 주의해야 한다. 우리는 게임 대상이 게임 표면에 제약을 받도록 확보해야 하고, 게임 대상의 위치를 설명하는 데 사용되는 좌표 단위를 의식적으로 결정해야 한다.우리는 이 절에서 이 두 문제를 토론할 것이다.

    편집하다


    기본적으로 Compose는 객체를 잘라내지 않고 단순하게 그립니다.이는 게임 대상이'게임 표면'을 찍을 수 있다는 것을 의미하며 이상한 네 번째 벽을 깨는 효과가 발생한다.

    우리는 Modifier.clipToBounds()Box에 적용함으로써 게임 대상을 게임 인터페이스의 경계에 제약한다. 이 경계는 우리의 게임 인터페이스를 정의한다.
    Box(modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight()
                    .clipToBounds()
                    // . . .
    
    우리의 모든 게임 요소는 이 게임 영역Box의 하위 요소로 그려졌기 때문에 이 수정기를 사용하면 그 중의 렌더링 실체가 가장자리에서 차단될 수 있다(주위의 사용자 인터페이스에 그려진 것이 아니라).

    장치와 무관한 픽셀 및 밀도


    Compose for Desktop에서 렌더링 작업을 수행할 때 주의해야 할 점은 측정 단위를 기억하는 것입니다.
    내가 어디에서 좌표를 사용하든지 간에 나는 device-independent pixels에서 일하기로 결정했다.
    마우스 포인터(91674)가 45a인 경우

  • 게임 폭 및 높이 저장DpOffsets

  • 게임 대상은 그Dp 좌표를 사용하여 게임 표면에 놓는다.
  • 이것은 게임이 고밀도 모니터와 저밀도 모니터에서 일치하도록 돕는 데 도움이 된다.그러나 그것도 .dp의 상하문에서 일부 조작을 실행해야 한다.
    예를 들어 Density 픽셀 단위로 되돌아오기pointerMoveFilter - 장치에 독립된 것이 아닙니다!이 문제를 해결하기 위해 우리는 구도에서 국부 화면 밀도를 얻었다.
    val density = LocalDensity.current
    
    그런 다음 Offset를 사용하여 with(density)의 확장 기능toDp()Offset에 액세스하여 장치에 독립적인 픽셀 형식으로 저장할 수 있습니다DpOffset.
    .pointerMoveFilter(onMove = {
        with(density) {
            game.targetLocation  = DpOffset(it.x.toDp(), it.y.toDp())
        }
        false
    })
    
    게임 영역의 폭과 높이를 저장하기 위해 우리는 매우 비슷한 일을 했다. 단지 그것을 targetLocation에 포장하지 않았다.
    .onSizeChanged {
        with(density) {
            game.width = it.width.toDp()
            game.height = it.height.toDp()
        }
    }
    

    기하와 선형 대수의 게임

    Underneath the visualization, the "Asteroids" game builds on just a few basic blocks to implement its mechanics – it is really a game of vectors and linear algebra:

    • The position, movement, and acceleration of the ship can be described by position, movement, and acceleration vectors.
    • The orientation of the ship is the angle of the vector between the ship and the cursor.
    • Circle-circle collisions can be tested based on distance vectors.
    Instead of reinventing the wheel vector, I decided to use DpOffset , which includes an implementation of the openrndr-math class including all common operations, like scalar multiplication, addition, subtraction, the dot product, and more. (Ever since listening to the Talking Kotlin라는 편에서 자세히 검토하고 싶었지만OPENRNDR 다른 프로젝트에서 진행해야 한다.)

    선형 대수 기술에 대해 좀 생소한 사람으로서 나는 이런 종류의 기능을 약간 확장했다.예를 들어, 0-360도 사이의 각도 aVector2에 액세스할 수 있도록 다음 확장 함수를 정의했습니다.
    fun Vector2.angle(): Double {
        val rawAngle = atan2(y = this.y, x = this.x)
        return (rawAngle / Math.PI) * 180
    }
    
    고맙게도, 나는 Vector2 에 대한 호출을 계산하는 데 너무 많은 시간을 쓰지 않았다. 왜냐하면 나는 이전에 그 중 하나를 보았기 때문에, 그도 이 함수를 사용하여 각도를 계산했다.
    이러한 확장은 내가 이해하는 방식으로 생각을 표현하는 데 도움을 주었다. 앞으로 몇 개월 동안 여전히 그러기를 바란다.
    나는 backing fields의 속성을 이용하여 atan2에 서로 다른 표현에서의 운동 방향량에 접근할 수 있다.
  • 길이(속도) 및 각도의 조합
  • GameObjectx 좌표
  • 의 벡터로
    예를 들어, ay의 컨텍스트는 다음과 같습니다.
    var speed by mutableStateOf(speed)
    var angle by mutableStateOf(angle)
    var position by mutableStateOf(position)
    var movementVector
        get() = (Vector2.UNIT_X * speed).rotate(angle)
        set(value) {
            speed = value.length
            angle = value.angle()
        }
    
    만약 우리가 GameObject클래스 이외에 이 기능을 자주 사용한다면 GameObject클래스에 추가length/anglegetter와setter를 확장 속성으로 직접 정의하는 것도 고려할 수 있다.
    시뮬레이션에 있어서, 우리는 더 많은 작업을 해야 한다. 우리는 아직 지나간 실시간으로 위치와 속도를 업데이트하는 문제를 해결하지 못했다.이어서 우리는 이런 방법을 토론해 봅시다.

    프레임과 무관한 동작, 증가 시간

    When building game logic, we need to keep one essential point in mind: Not all frames are created equal!

    • On a 60 Hz display, each frame is visible for 16ms.
    • On a 120 Hz display, that number drops to 8.3ms.
    • On a 240 Hz display, each frame only shows for 4.2ms.
    • On a system under load, or while running in a non-focused window, the application frame rate may be lower than 60 Hz.

    That means that we can't use "frames" as a measurement of time: If we define the speed of our spaceship in relation to the frame rate, it would move four times faster on a 240 Hz display than on a 60 Hz display.


    우리는 게임 논리(그리고 기본적인'물리 시뮬레이션')와 응용 프로그램이 실행하는 프레임 속도를 분리해야 한다.AAA games도 이 정도는 할 수 없지만, 우리 프로젝트에 대해서는 더 잘할 수 있습니다!
    이런 결합의 간단한 방법은 사용delta timing이다. 우리는 지난번에 게임을 업데이트한 이래의 시차(증량)에 따라 새로운 게임 상태를 계산한다.
    이것은 통상적으로 우리가 계산 결과를 시간 증량에 곱하여 경과 시간에 따라 결과를 축소한다는 것을 의미한다.

    Compose for Desktop에서는 Vector2withFrameMillis를 사용합니다.그것들은 모두 시간 스탬프를 제공하기 때문에 우리는 이전 시간 스탬프를 추적해서 계산하기만 하면 된다withFrameNanos.
    var prevTime = 0L
    
    fun update(time: Long) {
        val delta = time - prevTime
        // . . .
    
    나의 예에서 adelta에는 GameObject 함수가 하나 있는데 이것은 aupdate를 받아들인다.
    val velocity = movementVector * realDelta.toDouble()
    obj.position += velocity
    
    위 코드와 같이, 나는 그것을 사용하여 게임 대상의 속도를 조절한다.

    결어


    Compose for Desktop으로 작은 게임을 구축하는 과정은 이것으로 끝납니다!GitHub의 원본 코드(약 300줄 코드)를 읽고 이 코드들이 어떻게 조합되었는지 보세요!
    Compose for Desktop에 소행성을 구축하는 것은 매우 재미있습니다!나는 항상 Compose for Desktop가 제공하는 교체 속도에 놀랐다. 긴 밤에 첫 번째 직사각형에서 완전한 게임까지.
    물론 현대 하드웨어에서 소행성과 유사한 복고 게임을 실현하는 것은 성능 최적화, 분배, 실체 구성 요소 시스템 등을 지나치게 고려할 필요가 없다는 사치를 가져왔다.더 웅대한 것을 구축할 때, 이러한 요점은 해결해야 할 수도 있고, 당신은 realDelta: Float 실현 외에 추가 라이브러리를 사용했다는 것을 발견할 수 있습니다.
    그러나 다음Super Hexagon,pixel roguelike 또는 다른 2D 게임에 대해서는 절대로 시도해 볼 수 있다.
    마찬가지로, GitHub 에서 이 프로젝트의 모든 300줄 소스 코드를 찾을 수 있습니다.
    더 많은 영감을 찾고 있다면 다른 사람들이 Compose로 게임을 만드는 것을 보세요!
  • 웨이크 샤를르마 건조
  • vitaviva 건설
  • 존 오레리Compose for Desktop CHIP-8 frontend
  • theapache64는 Compose 내장 구성 요소의 실현 제한을 돌파했다
  • 좋은 웹페이지 즐겨찾기