Атлас спрайтов (frames), переделан с нуля
This commit is contained in:
parent
49e9dd2735
commit
8ef4ab0eb1
@ -15,6 +15,7 @@ import ru.dbotthepony.kstarbound.api.NonExistingFile
|
||||
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
||||
import ru.dbotthepony.kstarbound.api.explore
|
||||
import ru.dbotthepony.kstarbound.defs.*
|
||||
import ru.dbotthepony.kstarbound.defs.animation.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
||||
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
|
||||
@ -32,6 +33,7 @@ import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.util2d.AABBi
|
||||
@ -135,12 +137,13 @@ object Starbound {
|
||||
.registerTypeAdapter(stringTypeAdapter)
|
||||
|
||||
// math
|
||||
.registerTypeAdapter(AABB::class.java, AABBTypeAdapter)
|
||||
.registerTypeAdapter(AABBi::class.java, AABBiTypeAdapter)
|
||||
.registerTypeAdapter(Vector2d::class.java, Vector2dTypeAdapter)
|
||||
.registerTypeAdapter(Vector2f::class.java, Vector2fTypeAdapter)
|
||||
.registerTypeAdapter(Vector2i::class.java, Vector2iTypeAdapter)
|
||||
.registerTypeAdapter(Poly::class.java, PolyTypeAdapter)
|
||||
.registerTypeAdapter(AABBTypeAdapter)
|
||||
.registerTypeAdapter(AABBiTypeAdapter)
|
||||
.registerTypeAdapter(Vector2dTypeAdapter)
|
||||
.registerTypeAdapter(Vector2fTypeAdapter)
|
||||
.registerTypeAdapter(Vector2iTypeAdapter)
|
||||
.registerTypeAdapter(Vector4iTypeAdapter)
|
||||
.registerTypeAdapter(PolyTypeAdapter)
|
||||
|
||||
.also(ConfigurableProjectile::registerGson)
|
||||
.also(SkyParameters::registerGson)
|
||||
@ -154,6 +157,7 @@ object Starbound {
|
||||
.also(LiquidDefinition::registerGson)
|
||||
.also(ItemDefinition::registerGson)
|
||||
.also(ItemRarity::registerGson)
|
||||
.also(SpriteReference::registerGson)
|
||||
|
||||
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
||||
|
||||
|
@ -28,6 +28,10 @@ interface IStarboundFile {
|
||||
|
||||
fun orNull(): IStarboundFile? = if (exists) this else null
|
||||
|
||||
operator fun get(name: String): IStarboundFile? {
|
||||
return children?.get(name)
|
||||
}
|
||||
|
||||
fun computeFullPath(): String {
|
||||
var path = name
|
||||
var parent = parent
|
||||
|
@ -1,6 +1,5 @@
|
||||
package ru.dbotthepony.kstarbound.client.render.entity
|
||||
|
||||
import org.lwjgl.opengl.GL46
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.client.ClientChunk
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||
@ -11,28 +10,31 @@ import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||
|
||||
class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) {
|
||||
private val def = entity.def
|
||||
private val texture = def.inventoryIcon?.let(state::loadNamedTextureSafe)
|
||||
private val textures = def.inventoryIcon?.stream()?.map { state.loadNamedTextureSafe(it.image.path) }?.toList() ?: listOf()
|
||||
|
||||
override fun render(stack: Matrix4fStack) {
|
||||
if (texture == null)
|
||||
if (textures.isEmpty())
|
||||
return
|
||||
|
||||
state.shaderVertexTexture.use()
|
||||
state.shaderVertexTexture.transform.set(stack.last)
|
||||
state.activeTexture = 0
|
||||
state.shaderVertexTexture["_texture"] = 0
|
||||
texture.bind()
|
||||
|
||||
val builder = state.flat2DTexturedQuads.small
|
||||
for (texture in textures) {
|
||||
texture.bind()
|
||||
|
||||
builder.begin()
|
||||
val builder = state.flat2DTexturedQuads.small
|
||||
|
||||
val width = (texture.width / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
val height = (texture.height / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
builder.begin()
|
||||
|
||||
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, QuadTransformers.uv(0f, 1f, 1f, 0f))
|
||||
val width = (texture.width / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
val height = (texture.height / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, QuadTransformers.uv(0f, 1f, 1f, 0f))
|
||||
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,233 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
import com.google.gson.stream.JsonReader
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.io.json.stream
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector4i
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Атлас спрайтов, собранный вручную артистом
|
||||
*
|
||||
* В файлах игры именуется frames
|
||||
*/
|
||||
class AtlasDefinition private constructor(
|
||||
val name: String,
|
||||
|
||||
/**
|
||||
* Спрайты данного атласа, включая спрайты под псевдонимами
|
||||
*/
|
||||
val sprites: ImmutableMap<String, Sprite>,
|
||||
) {
|
||||
/**
|
||||
* Первый спрайт, по своему имени (естественная сортировка)
|
||||
*/
|
||||
val first: Sprite = sprites[sprites.keys.stream().sorted().findFirst().orElseThrow { NoSuchElementException("No a single key present in $name") }] ?: throw NoSuchElementException("IMPOSSIBRU in $name")
|
||||
|
||||
operator fun get(name: String): Sprite? {
|
||||
return sprites[name]
|
||||
}
|
||||
|
||||
fun any(name: String): Sprite {
|
||||
return get(name) ?: first
|
||||
}
|
||||
|
||||
fun any(): Sprite {
|
||||
return get("root") ?: get("0") ?: first
|
||||
}
|
||||
|
||||
class Sprite(
|
||||
/**
|
||||
* Имя данного спрайта, на которое прототипы могут ссылаться
|
||||
*
|
||||
* К примеру, если имя будет указано как
|
||||
*
|
||||
* **`active.2`**
|
||||
*
|
||||
* а сам атлас называется
|
||||
*
|
||||
* **`mything.png`**
|
||||
*
|
||||
* то прототипы могут ссылаться на данный спрайт как
|
||||
*
|
||||
* **`mything.png:active.2`**
|
||||
*/
|
||||
val name: String,
|
||||
|
||||
/**
|
||||
* Позиция и размеры данного спрайта, в пикселях
|
||||
*/
|
||||
val position: Vector4i,
|
||||
) {
|
||||
/**
|
||||
* Вычисляет uv координаты данного спрайта на заданном полотне
|
||||
*/
|
||||
fun compute(width: Int, height: Int): UVCoordinates {
|
||||
return UVCoordinates(
|
||||
u0 = position.x.toFloat() / width.toFloat(),
|
||||
v0 = position.y.toFloat() / height.toFloat(),
|
||||
u1 = position.z.toFloat() / width.toFloat(),
|
||||
v1 = position.w.toFloat() / height.toFloat(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисляет uv координаты данного спрайта на заданном полотне
|
||||
*/
|
||||
fun compute(dimensions: Vector2i): UVCoordinates {
|
||||
return compute(dimensions.x, dimensions.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисляет uv координаты данного спрайта на заданном полотне
|
||||
*/
|
||||
fun compute(texture: GLTexture2D): UVCoordinates {
|
||||
return compute(texture.width, texture.height)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FrameGrid.Sprite[name=$name, position=$position]"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = AtlasDefinition("null", ImmutableMap.of("root", Sprite("root", Vector4i(0, 0, 1, 1))))
|
||||
|
||||
private val cache = ConcurrentHashMap<String, AtlasDefinition>()
|
||||
|
||||
private fun generateFakeNames(dimensions: Vector2i): JsonArray {
|
||||
return JsonArray(dimensions.y).also {
|
||||
var stripElem = 0
|
||||
|
||||
for (stripNum in 0 until dimensions.y) {
|
||||
val strip = JsonArray(dimensions.x)
|
||||
|
||||
for (i in 0 until dimensions.x) {
|
||||
strip.add(stripElem.toString())
|
||||
stripElem++
|
||||
}
|
||||
|
||||
it.add(strip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFrames(input: JsonReader, name: String): AtlasDefinition {
|
||||
val read = TypeAdapters.JSON_ELEMENT.read(input)
|
||||
|
||||
if (read !is JsonObject) {
|
||||
throw JsonSyntaxException("Expected to have object as top most element, $read given")
|
||||
}
|
||||
|
||||
val frameGrid = read["frameGrid"]
|
||||
val sprites = HashMap<String, Sprite>()
|
||||
|
||||
if (frameGrid is JsonObject) {
|
||||
val size = Starbound.gson.fromJson(frameGrid["size"] ?: throw JsonSyntaxException("Missing frameGrid.size"), Vector2i::class.java)
|
||||
val dimensions = Starbound.gson.fromJson(frameGrid["dimensions"] ?: throw JsonSyntaxException("Missing frameGrid.dimensions"), Vector2i::class.java)
|
||||
|
||||
require(size.x >= 0) { "Invalid size.x: ${size.x}" }
|
||||
require(size.y >= 0) { "Invalid size.y: ${size.y}" }
|
||||
|
||||
require(dimensions.x >= 0) { "Invalid dimensions.x: ${dimensions.x}" }
|
||||
require(dimensions.y >= 0) { "Invalid dimensions.y: ${dimensions.y}" }
|
||||
|
||||
val names = (frameGrid["names"] as? JsonArray ?: generateFakeNames(dimensions))
|
||||
.stream().map { (it as? JsonArray)?.stream()?.map { if (it == JsonNull.INSTANCE) null else it.asString }?.toList() }.toList()
|
||||
|
||||
val spriteList = ArrayList<Sprite>()
|
||||
|
||||
for ((y, strip) in names.withIndex()) {
|
||||
// разрешаем вставлять null как ленту кадров, что означает что мы должны пропустить её полностью
|
||||
if (strip == null)
|
||||
continue
|
||||
|
||||
for ((x, spriteName) in strip.withIndex()) {
|
||||
// если кадр не имеет имени...
|
||||
if (spriteName == null)
|
||||
continue
|
||||
|
||||
spriteList.add(Sprite(spriteName, Vector4i(x * size.x, y * size.y, (x + 1) * size.x, (y + 1) * size.y)))
|
||||
}
|
||||
}
|
||||
|
||||
for (sprite in spriteList)
|
||||
sprites[sprite.name] = sprite
|
||||
} else if (frameGrid != null) {
|
||||
throw JsonSyntaxException("Unexpected frameGrid element: $frameGrid")
|
||||
} else {
|
||||
val frameList = read["frameList"] ?: throw JsonSyntaxException("Frame grid must have either frameGrid or frameList object defined")
|
||||
|
||||
if (frameList !is JsonObject) {
|
||||
throw JsonSyntaxException("frameList is not an object")
|
||||
}
|
||||
|
||||
for ((spriteName, coords) in frameList.entrySet()) {
|
||||
sprites[spriteName] = Sprite(spriteName, Starbound.gson.fromJson(coords, Vector4i::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
val aliases = read["aliases"]
|
||||
|
||||
if (aliases != null) {
|
||||
if (aliases !is JsonObject)
|
||||
throw JsonSyntaxException("aliases expected to be a Json object, $aliases given")
|
||||
|
||||
for ((k, v) in aliases.entrySet())
|
||||
sprites[k] = sprites[v.asString] ?: throw JsonSyntaxException("$k want to refer to sprite $v, but it does not exist")
|
||||
}
|
||||
|
||||
return AtlasDefinition(name, ImmutableMap.copyOf(sprites))
|
||||
}
|
||||
|
||||
private fun recursiveGet(name: String, folder: String): AtlasDefinition? {
|
||||
var current = folder
|
||||
|
||||
while (current != "/" && current != "") {
|
||||
val get = cache.computeIfAbsent("$current/$name") {
|
||||
val file = Starbound.locate("$it.frames")
|
||||
|
||||
if (file.exists) {
|
||||
try {
|
||||
return@computeIfAbsent parseFrames(JsonReader(file.reader()), "$it.frames")
|
||||
} catch (err: Throwable) {
|
||||
throw JsonSyntaxException("Reading frame grid $it.frames")
|
||||
}
|
||||
}
|
||||
|
||||
return@computeIfAbsent EMPTY
|
||||
}
|
||||
|
||||
if (get !== EMPTY) {
|
||||
return get
|
||||
}
|
||||
|
||||
current = current.substringBeforeLast('/')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun get(path: String): AtlasDefinition {
|
||||
require(path[0] == '/') { "$path is not an absolute path" }
|
||||
val folder = path.substringBeforeLast('/').lowercase()
|
||||
val filename = path.substringAfterLast('/').substringBefore('.').lowercase()
|
||||
|
||||
val direct = recursiveGet(filename, folder)
|
||||
if (direct != null) return direct
|
||||
|
||||
val default = recursiveGet("default", folder)
|
||||
if (default != null) return default
|
||||
|
||||
return EMPTY
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import ru.dbotthepony.kvector.api.IStruct4f
|
||||
|
||||
interface IUVCoordinates : IStruct4f {
|
||||
val u0: Float
|
||||
val v0: Float
|
||||
val u1: Float
|
||||
val v1: Float
|
||||
|
||||
override fun component1(): Float {
|
||||
return u0
|
||||
}
|
||||
|
||||
override fun component2(): Float {
|
||||
return v0
|
||||
}
|
||||
|
||||
override fun component3(): Float {
|
||||
return u1
|
||||
}
|
||||
|
||||
override fun component4(): Float {
|
||||
return v1
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.registerTypeAdapter
|
||||
|
||||
data class SpriteReference(
|
||||
val path: String,
|
||||
val sprite: AtlasDefinition.Sprite
|
||||
) {
|
||||
companion object : TypeAdapter<SpriteReference>() {
|
||||
fun parse(input: String): SpriteReference {
|
||||
val grid = AtlasDefinition.get(input.substringBefore(':'))
|
||||
|
||||
return when (input.count { it == ':' }) {
|
||||
0 -> SpriteReference(input, grid.any())
|
||||
1 -> SpriteReference(input.substringBefore(':'), grid.get(input.substringAfter(':')) ?: throw NoSuchElementException("No such sprite with name ${input.substringAfter(':')} present in frame grid ${grid.name} (atlas ${input.substringBefore(':')})"))
|
||||
else -> throw IllegalArgumentException("Invalid sprite reference: $input")
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: JsonWriter, value: SpriteReference) {
|
||||
out.value(value.path + ":" + value.sprite.name)
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): SpriteReference {
|
||||
return parse(Starbound.readingFolderTransformer(`in`.nextString()))
|
||||
}
|
||||
|
||||
fun registerGson(gsonBuilder: GsonBuilder) {
|
||||
gsonBuilder.registerTypeAdapter(this)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
data class UVCoordinates(
|
||||
override val u0: Float,
|
||||
override val v0: Float,
|
||||
override val u1: Float,
|
||||
override val v1: Float,
|
||||
) : IUVCoordinates
|
@ -1,12 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.animation.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.io.json.KConcreteTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.ListAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.asJsonObject
|
||||
import ru.dbotthepony.kstarbound.io.json.asList
|
||||
import ru.dbotthepony.kstarbound.io.json.ifString
|
||||
@ -37,7 +35,7 @@ data class ItemDefinition(
|
||||
/**
|
||||
* Иконка в инвентаре, относительный и абсолютный пути
|
||||
*/
|
||||
val inventoryIcon: String? = null,
|
||||
val inventoryIcon: List<InventoryIcon>? = null,
|
||||
|
||||
/**
|
||||
* Описание предмета
|
||||
@ -260,13 +258,21 @@ data class ItemDefinition(
|
||||
val amount: Double = 0.0,
|
||||
)
|
||||
|
||||
data class InventoryIcon(
|
||||
val image: SpriteReference
|
||||
)
|
||||
|
||||
companion object {
|
||||
val INVENTORY_ICON_ADAPTER = KConcreteTypeAdapter.Builder(InventoryIcon::class)
|
||||
.auto(InventoryIcon::image)
|
||||
.build()
|
||||
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(ItemDefinition::class)
|
||||
.auto(ItemDefinition::itemName)
|
||||
.auto(ItemDefinition::price)
|
||||
.auto(ItemDefinition::rarity)
|
||||
.auto(ItemDefinition::category)
|
||||
.auto(ItemDefinition::inventoryIcon, Starbound::readingFolderTransformerNullable)
|
||||
.add(ItemDefinition::inventoryIcon, ListAdapter(INVENTORY_ICON_ADAPTER).ifString { listOf(InventoryIcon(SpriteReference.parse(Starbound.readingFolderTransformer(it)))) }.nullSafe())
|
||||
.auto(ItemDefinition::description)
|
||||
.auto(ItemDefinition::shortdescription)
|
||||
|
||||
@ -342,6 +348,7 @@ data class ItemDefinition(
|
||||
gsonBuilder.registerTypeAdapter(FOSSIL_ADAPTER)
|
||||
gsonBuilder.registerTypeAdapter(ARMOR_FRAMES_ADAPTER)
|
||||
gsonBuilder.registerTypeAdapter(STATUS_EFFECT_ADAPTER)
|
||||
gsonBuilder.registerTypeAdapter(INVENTORY_ICON_ADAPTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package ru.dbotthepony.kstarbound.io.json
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSpliterator
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSpliterators
|
||||
import java.util.stream.Stream
|
||||
import java.util.stream.StreamSupport
|
||||
|
||||
class JsonArraySpliterator(private val obj: JsonArray, offset: Int = 0, private val maxPos: Int = obj.size()) : ObjectSpliterators.AbstractIndexBasedSpliterator<JsonElement>(offset) {
|
||||
init {
|
||||
require(offset >= 0) { "Invalid offset $offset" }
|
||||
require(offset + maxPos <= obj.size()) { "$offset -> $maxPos while having only size of ${obj.size()}!" }
|
||||
}
|
||||
|
||||
override fun get(location: Int): JsonElement {
|
||||
return obj[location]
|
||||
}
|
||||
|
||||
override fun getMaxPos(): Int {
|
||||
return maxPos
|
||||
}
|
||||
|
||||
override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator<JsonElement> {
|
||||
return JsonArraySpliterator(obj, pos, maxPos)
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonArray.elementSpliterator() = JsonArraySpliterator(this)
|
||||
fun JsonArray.stream(): Stream<out JsonElement> = StreamSupport.stream(elementSpliterator(), false)
|
@ -6,6 +6,31 @@ import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector4i
|
||||
|
||||
object Vector4iTypeAdapter : TypeAdapter<Vector4i>() {
|
||||
override fun write(out: JsonWriter, value: Vector4i) {
|
||||
`out`.beginArray()
|
||||
`out`.value(value.x)
|
||||
`out`.value(value.y)
|
||||
`out`.value(value.z)
|
||||
`out`.value(value.w)
|
||||
`out`.endArray()
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): Vector4i {
|
||||
`in`.beginArray()
|
||||
|
||||
val x = `in`.nextInt()
|
||||
val y = `in`.nextInt()
|
||||
val z = `in`.nextInt()
|
||||
val w = `in`.nextInt()
|
||||
|
||||
`in`.endArray()
|
||||
|
||||
return Vector4i(x, y, z, w)
|
||||
}
|
||||
}
|
||||
|
||||
object Vector2iTypeAdapter : TypeAdapter<Vector2i>() {
|
||||
override fun write(out: JsonWriter, value: Vector2i) {
|
||||
|
Loading…
Reference in New Issue
Block a user