Skip to content

Commit

Permalink
refactor Aggro logic to use correct size of sensors for state logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Quillraven committed Mar 31, 2024
1 parent 28246aa commit 4e322dc
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 80 deletions.
6 changes: 3 additions & 3 deletions assets/maps/objects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
<property name="hasAggro" type="bool" value="true"/>
<property name="hasAnimation" type="bool" value="true"/>
<property name="initialState" value="ROCK_HEAD_IDLE"/>
<property name="speed" type="float" value="12"/>
<property name="speed" type="float" value="9"/>
<property name="timeToMaxSpeed" type="float" value="3"/>
</properties>
<image width="42" height="42" source="../graphics/object/rock-head.png"/>
Expand All @@ -84,14 +84,14 @@
<property name="userData" value="hitbox"/>
</properties>
</object>
<object id="2" type="FixtureDef" x="0" y="-64" width="42" height="170">
<object id="2" type="FixtureDef" x="6" y="-64" width="30" height="170">
<properties>
<property name="isChain" type="bool" value="false"/>
<property name="isSensor" type="bool" value="true"/>
<property name="userData" value="aggroSensor"/>
</properties>
</object>
<object id="3" type="FixtureDef" x="-64" y="0" width="170" height="42">
<object id="3" type="FixtureDef" x="-64" y="6" width="170" height="30">
<properties>
<property name="isChain" type="bool" value="false"/>
<property name="isSensor" type="bool" value="true"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ enum class GameObjectState : State<AiEntity> {
}

override fun update(entity: AiEntity) {
val (_, targetEntity) = entity[Aggro]
val (_, targetEntity, _, range) = entity[Aggro]
if (targetEntity == Entity.NONE) {
entity.state(ROCK_HEAD_IDLE)
} else if (entity.inRange(targetEntity, ROCK_HEAD_ATTACK_RANGE)) {
} else if (entity.inRange(targetEntity, range * 0.9f)) {
entity.state(ROCK_HEAD_ATTACK)
}
}
Expand All @@ -130,17 +130,17 @@ enum class GameObjectState : State<AiEntity> {
val (_, targetEntity) = entity[Aggro]
val angle = entity.angleTo(targetEntity)
when {
angle <= PI * 0.25f && angle >= -PI * 0.25f -> entity[Move].direction = MoveDirection.RIGHT
angle <= -PI * 0.25f && angle >= -PI * 0.75f -> entity[Move].direction = MoveDirection.DOWN
angle >= PI * 0.75f || angle <= -PI * 0.75f -> entity[Move].direction = MoveDirection.LEFT
angle <= QUARTER_PI && angle >= -QUARTER_PI -> entity[Move].direction = MoveDirection.RIGHT
angle <= -QUARTER_PI && angle >= -THREE_QUARTER_PI -> entity[Move].direction = MoveDirection.DOWN
angle >= THREE_QUARTER_PI || angle <= -THREE_QUARTER_PI -> entity[Move].direction = MoveDirection.LEFT
else -> entity[Move].direction = MoveDirection.UP
}
}

override fun update(entity: AiEntity) {
val (_, _, sourceLocation) = entity[Aggro]
val (_, _, sourceLocation, range) = entity[Aggro]

if (entity.notInRange(sourceLocation, 7f)) {
if (entity.notInRange(sourceLocation, range)) {
entity.state(ROCK_HEAD_RETURN)
}
}
Expand Down Expand Up @@ -172,6 +172,7 @@ enum class GameObjectState : State<AiEntity> {
override fun onMessage(entity: AiEntity, telegram: Telegram) = false

companion object {
private const val ROCK_HEAD_ATTACK_RANGE = 5f
private const val QUARTER_PI = PI * 0.25f
private const val THREE_QUARTER_PI = PI * 0.75f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class Aggro(
val aggroEntities: MutableList<Entity> = mutableListOf(),
var target: Entity = Entity.NONE,
val sourceLocation: Vector2,
val range: Float,
) : Component<Aggro> {
override fun type() = Aggro

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,19 @@ class PhysicSystem(
}

when {
moveCmp.direction.isNone() -> {
// no direction specified -> stop movement
if (body.type == DynamicBody) {
// gravity impacts dynamic bodies -> keep current linear velocity of the y-axis
body.setLinearVelocity(0f, body.linearVelocity.y)
} else {
// other bodies are not impacted by gravity -> just stop them
body.setLinearVelocity(0f, 0f)
}
// no direction specified -> stop movement
moveCmp.direction.isNone() -> when (body.type) {
// gravity impacts dynamic bodies -> keep current linear velocity of the y-axis
DynamicBody -> body.setLinearVelocity(0f, body.linearVelocity.y)
// other bodies are not impacted by gravity -> just stop them
else -> body.setLinearVelocity(0f, 0f)
}

moveCmp.direction.isLeftOrRight() -> {
// horicontal movement keeps the gravity value (=linear velocity of the y-axis)
body.setLinearVelocity(moveCmp.current, body.linearVelocity.y)
}
// horizontal movement keeps the gravity value (=linear velocity of the y-axis)
moveCmp.direction.isLeftOrRight() -> body.setLinearVelocity(moveCmp.current, body.linearVelocity.y)

else -> {
// vertical movement is limited and does not apply a velocity on the x-axis
body.setLinearVelocity(0f, moveCmp.current)
}
// vertical movement is limited and does not apply a velocity on the x-axis
else -> body.setLinearVelocity(0f, moveCmp.current)
}
}

Expand All @@ -93,25 +86,6 @@ class PhysicSystem(
)
}

private val Fixture.entity: Entity?
get() {
val userData = this.body.userData
if (userData is Entity) {
return userData
}
return null
}

private val Contact.entityA: Entity?
get() = fixtureA.entity

private val Contact.entityB: Entity?
get() = fixtureB.entity

private fun Fixture.isHitbox(): Boolean = "hitbox" == userData

private fun Fixture.isAggroSensor(): Boolean = isSensor && "aggroSensor" == userData

override fun beginContact(contact: Contact) {
val fixtureA = contact.fixtureA
val fixtureB = contact.fixtureB
Expand Down Expand Up @@ -218,3 +192,23 @@ class PhysicSystem(
}

}

private val Fixture.entity: Entity?
get() {
val userData = this.body.userData
if (userData is Entity) {
return userData
}
return null
}

private val Contact.entityA: Entity?
get() = fixtureA.entity

private val Contact.entityB: Entity?
get() = fixtureB.entity

private fun Fixture.isHitbox(): Boolean = "hitbox" == userData

const val USER_DATA_AGGRO_SENSOR = "aggroSensor"
fun Fixture.isAggroSensor(): Boolean = isSensor && USER_DATA_AGGRO_SENSOR == userData
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import ktx.tiled.*

typealias GdxFloatArray = com.badlogic.gdx.utils.FloatArray

data class FixtureDefUserData(val def: FixtureDef, val userData: String)
data class FixtureDefUserData(val def: FixtureDef, val userData: String, val size: Vector2)

class TiledService(
private val world: World,
Expand Down Expand Up @@ -131,7 +131,7 @@ class TiledService(
configureMove(it, tile)
configureDamage(it, tile)
configureTrack(it, mapObject, trackLayer)
configureAggro(it, tile)
configureAggro(it, tile, gameObject)

log.debug {
"""Spawning entity with:
Expand Down Expand Up @@ -176,37 +176,41 @@ class TiledService(

fun MapLayer.isObjectsLayer(): Boolean = this.name == "objects"

private val MapObject.userData: String
get() = property("userData", "")

fun fixtureDefOf(mapObject: MapObject): FixtureDefUserData {
val fixtureDef = when (mapObject) {
is RectangleMapObject -> rectangleFixtureDef(mapObject)
is RectangleMapObject -> rectangleFixtureDef(mapObject, mapObject.userData)
is EllipseMapObject -> ellipseFixtureDef(mapObject)
is PolygonMapObject -> polygonFixtureDef(mapObject)
is PolylineMapObject -> polylineFixtureDef(mapObject)
else -> gdxError("Unsupported MapObject $mapObject")
}

fixtureDef.friction = mapObject.property("friction", 0f)
fixtureDef.restitution = mapObject.property("restitution", 0f)
fixtureDef.density = mapObject.property("density", 0f)
fixtureDef.isSensor = mapObject.property("isSensor", false)
fixtureDef.def.friction = mapObject.property("friction", 0f)
fixtureDef.def.restitution = mapObject.property("restitution", 0f)
fixtureDef.def.density = mapObject.property("density", 0f)
fixtureDef.def.isSensor = mapObject.property("isSensor", false)

return FixtureDefUserData(fixtureDef, mapObject.property("userData", ""))
return fixtureDef
}

private fun polylineFixtureDef(mapObject: PolylineMapObject): FixtureDef {
return polygonFixtureDef(mapObject.x, mapObject.y, mapObject.polyline.vertices, false)
private fun polylineFixtureDef(mapObject: PolylineMapObject): FixtureDefUserData {
return polygonFixtureDef(mapObject.x, mapObject.y, mapObject.polyline.vertices, false, mapObject.userData)
}

private fun polygonFixtureDef(mapObject: PolygonMapObject): FixtureDef {
return polygonFixtureDef(mapObject.x, mapObject.y, mapObject.polygon.vertices, true)
private fun polygonFixtureDef(mapObject: PolygonMapObject): FixtureDefUserData {
return polygonFixtureDef(mapObject.x, mapObject.y, mapObject.polygon.vertices, true, mapObject.userData)
}

private fun polygonFixtureDef(
polyX: Float,
polyY: Float,
polyVertices: FloatArray,
loop: Boolean,
): FixtureDef {
userData: String,
): FixtureDefUserData {
val x = polyX * UNIT_SCALE
val y = polyY * UNIT_SCALE
val vertices = FloatArray(polyVertices.size) { vertexIdx ->
Expand All @@ -217,7 +221,7 @@ class TiledService(
}
}

return FixtureDef().apply {
val def = FixtureDef().apply {
shape = ChainShape().apply {
if (loop) {
createLoop(vertices)
Expand All @@ -226,9 +230,10 @@ class TiledService(
}
}
}
return FixtureDefUserData(def, userData, Vector2.Zero)
}

private fun ellipseFixtureDef(mapObject: EllipseMapObject): FixtureDef {
private fun ellipseFixtureDef(mapObject: EllipseMapObject): FixtureDefUserData {
val (x, y, w, h) = mapObject.ellipse
val ellipseX = x * UNIT_SCALE
val ellipseY = y * UNIT_SCALE
Expand All @@ -237,12 +242,13 @@ class TiledService(

return if (MathUtils.isEqual(ellipseW, ellipseH, 0.1f)) {
// width and height are equal -> return a circle shape
FixtureDef().apply {
val def = FixtureDef().apply {
shape = CircleShape().apply {
position = vec2(ellipseX + ellipseW, ellipseY + ellipseH)
radius = ellipseW
}
}
FixtureDefUserData(def, mapObject.userData, vec2(ellipseW, ellipseH))
} else {
// width and height are not equal -> return an ellipse shape (=polygon with 'numVertices' vertices)
val numVertices = 20
Expand All @@ -254,18 +260,19 @@ class TiledService(
vec2(ellipseX + ellipseW + offsetX, ellipseY + ellipseH + offsetY)
}

FixtureDef().apply {
val def = FixtureDef().apply {
shape = ChainShape().apply {
createLoop(vertices)
}
}
FixtureDefUserData(def, mapObject.userData, vec2(ellipseW, ellipseH))
}
}

// box is centered around body position in Box2D, but we want to have it aligned in a way
// that the body position is the bottom left corner of the box.
// That's why we use a 'boxOffset' below.
private fun rectangleFixtureDef(mapObject: RectangleMapObject): FixtureDef {
private fun rectangleFixtureDef(mapObject: RectangleMapObject, userData: String): FixtureDefUserData {
val (rectX, rectY, rectW, rectH) = mapObject.rectangle
val boxX = rectX * UNIT_SCALE
val boxY = rectY * UNIT_SCALE
Expand All @@ -282,21 +289,23 @@ class TiledService(
vec2(boxX, boxY + boxH),
)

return FixtureDef().apply {
val def = FixtureDef().apply {
shape = ChainShape().apply {
createLoop(vertices)
}
}
return FixtureDefUserData(def, userData, vec2(boxW, boxH))
}

// create a box
val boxW = rectW * UNIT_SCALE * 0.5f
val boxH = rectH * UNIT_SCALE * 0.5f
return FixtureDef().apply {
val def = FixtureDef().apply {
shape = PolygonShape().apply {
setAsBox(boxW, boxH, vec2(boxX + boxW, boxY + boxH), 0f)
}
}
return FixtureDefUserData(def, userData, vec2(boxW, boxH))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.EntityCreateContext
import com.github.quillraven.fleks.World
import com.quillraven.github.quillyjumper.GameObject
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.OBJECT_FIXTURES
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.UNIT_SCALE
import com.quillraven.github.quillyjumper.ai.AiEntity
import com.quillraven.github.quillyjumper.ai.GameObjectState
import com.quillraven.github.quillyjumper.component.*
import com.quillraven.github.quillyjumper.system.USER_DATA_AGGRO_SENSOR
import ktx.app.gdxError
import ktx.math.vec2
import ktx.tiled.*
import kotlin.math.max

fun EntityCreateContext.configureEntityTags(entity: Entity, tile: TiledMapTile) {
val tagsStr = tile.property<String>("entityTags", "")
Expand Down Expand Up @@ -109,9 +112,12 @@ private fun MapLayer.trackCmpOf(mapObject: MapObject): Track {
gdxError("There is no related track for MapObject ${mapObject.id}")
}

fun EntityCreateContext.configureAggro(entity: Entity, tile: TiledMapTile) {
fun EntityCreateContext.configureAggro(entity: Entity, tile: TiledMapTile, gameObject: GameObject) {
val hasAggro = tile.property<Boolean>("hasAggro", false)
if (hasAggro) {
entity += Aggro(sourceLocation = entity[Graphic].center.cpy())
val aggroDef = OBJECT_FIXTURES[gameObject]?.first { it.def.isSensor && it.userData == USER_DATA_AGGRO_SENSOR }
?: gdxError("There is no aggroSensor for entity $entity")
val range = max(aggroDef.size.x, aggroDef.size.y)
entity += Aggro(sourceLocation = entity[Graphic].center.cpy(), range = range)
}
}
Loading

0 comments on commit 4e322dc

Please sign in to comment.