Jetpack Compose for Desktop을 사용한 게임 구성 팁
렌더링 디테일 - 게임 대상이 게임 영역에서 벗어나지 않도록 하고 장치에 독립된 좌표계를 사용하여 렌더링
기하학과 선형대수-우주선을 비행시키는 비밀소스
프레임 독립 운동 - 우리의 게임을 시종일관 한결같이 하자.
렌더링:자르기 및 좌표계
과장된 환경에서 두 가지 측면이 여전히 우리가 주의해야 한다. 우리는 게임 대상이 게임 표면에 제약을 받도록 확보해야 하고, 게임 대상의 위치를 설명하는 데 사용되는 좌표 단위를 의식적으로 결정해야 한다.우리는 이 절에서 이 두 문제를 토론할 것이다.
편집하다
기본적으로 Compose는 객체를 잘라내지 않고 단순하게 그립니다.이는 게임 대상이'게임 표면'을 찍을 수 있다는 것을 의미하며 이상한 네 번째 벽을 깨는 효과가 발생한다.
우리는
Modifier.clipToBounds()
를 Box
에 적용함으로써 게임 대상을 게임 인터페이스의 경계에 제약한다. 이 경계는 우리의 게임 인터페이스를 정의한다.Box(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.clipToBounds()
// . . .
우리의 모든 게임 요소는 이 게임 영역Box
의 하위 요소로 그려졌기 때문에 이 수정기를 사용하면 그 중의 렌더링 실체가 가장자리에서 차단될 수 있다(주위의 사용자 인터페이스에 그려진 것이 아니라).장치와 무관한 픽셀 및 밀도
Compose for Desktop에서 렌더링 작업을 수행할 때 주의해야 할 점은 측정 단위를 기억하는 것입니다.
내가 어디에서 좌표를 사용하든지 간에 나는 device-independent pixels에서 일하기로 결정했다.
마우스 포인터(91674)가 45a인 경우
게임 폭 및 높이 저장
DpOffset
s게임 대상은 그
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.
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도 사이의 각도 a
Vector2
에 액세스할 수 있도록 다음 확장 함수를 정의했습니다.fun Vector2.angle(): Double {
val rawAngle = atan2(y = this.y, x = this.x)
return (rawAngle / Math.PI) * 180
}
고맙게도, 나는 Vector2
에 대한 호출을 계산하는 데 너무 많은 시간을 쓰지 않았다. 왜냐하면 나는 이전에 그 중 하나를 보았기 때문에, 그도 이 함수를 사용하여 각도를 계산했다.이러한 확장은 내가 이해하는 방식으로 생각을 표현하는 데 도움을 주었다. 앞으로 몇 개월 동안 여전히 그러기를 바란다.
나는 backing fields의 속성을 이용하여
atan2
에 서로 다른 표현에서의 운동 방향량에 접근할 수 있다.GameObject
와 x
좌표예를 들어, a
y
의 컨텍스트는 다음과 같습니다.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
/angle
getter와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에서는
Vector2
및 withFrameMillis
를 사용합니다.그것들은 모두 시간 스탬프를 제공하기 때문에 우리는 이전 시간 스탬프를 추적해서 계산하기만 하면 된다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로 게임을 만드는 것을 보세요!
Reference
이 문제에 관하여(Jetpack Compose for Desktop을 사용한 게임 구성 팁), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/kotlin/tips-tricks-for-building-a-game-using-jetpack-compose-for-desktop-266o텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)