heyx3 / Bplus.jl

A modern OpenGL 4.6 rendering framework, written in Julia.
Other
72 stars 3 forks source link

Entity-Component System #114

Closed heyx3 closed 10 months ago

heyx3 commented 11 months ago

Take from Drill8, and implement macros to match this sample usage. Also bring over the unit tests and upgrade them (e.x. aknowledging component/entity destruction)

"Some kind of movement animation that plays out over a certain desired interval"
@abstract_component AbstractManeuver {entitySingleton} {require: ContinuousPosition} begin
    cosmetic_shake::CosmeticOffsetComponent
    pos::ContinuousPosition

    maneuver_duration::Float32 # seconds
    progress::Float32 # 0 to 1

    function CONSTRUCT(duration_seconds::Float32)
        @bp_check(!has_component(entity, AbstractManeuver),
                  "Entity is already in the middle of a maneuver")

        this.cosmetic_shake = add_component(entity, CosmeticOffset)
        this.pos = get_component(entity, ContinuousPosition)
        this.maneuver_duration = duration_seconds
        this.progress = 0
    end
    function DESTRUCT(entity_is_dying::Bool) # Last arg not needed if not wanted
        if !entity_is_dying
            remove_component(entity, this.cosmetic_shake)
        end
    end

    "Complete the animation (e.x. teleport to the final position)"
    @promise finish()

    "Report the current strength of the component's shaking"
    @configurable function get_shake_strengths()::Vec{NShakeModes, Float32}
        return zero(Vec{NShakeModes, Float32})
    end

    function TICK()
        this.progress += world.delta_seconds / this.maneuver_duration

        # Update shaking animation.
        shake_data = zip(
            this.get_shake_strengths(),
            (
                CAB_SHAKE_MODES[i](world.elapsed_seconds)
                  for i in 1:NShakeModes
            )
        )
        this.cosmetic_shake.pos = sum(strength * state.pos for (strength, state) in shake_data)
        this.cosmetic_shake.rot = let rot = fquat()
            for (strength, state) in shake_data
                rot >>= fquat(get_up_vector(), state.yaw * strength)
                rot >>= fquat(get_horz_vector(2), state.pitch * strength)
                rot >>= fquat(get_horz_vector(1), state.roll * strength)
            end
            rot
        end
    end
    function POST_TICK()
        # If the animation is finished, clean up.
        if this.progress >= 1
            this.finish()
            remove_component(entity, this)
        end
    end
end

@component CabTurnComponent<:AbstractManeuver {require: Orientation} begin
    target::fquat
    entity_rot::Orientation

    CONSTRUCT(target::fquat) = begin
        this.target = target
        this.entity_rot = get_component(entity, Orientation)
    end

    get_shake_strengths() = zero(Vec{NShakeModes, Float32})
    finish() = (this.entity_rot.rot = this.target)

    TICK() = begin
        # Calculate the full turn needed to make it to the target.
        forward::v3f = q_apply(this.entity_rot.rot, get_horz_vector(1))
        desired_forward::v3f = q_apply(this.target, get_horz_vector(1))
        full_turn = fquat(forward, desired_forward)
        (turn_axis, turn_radians) = q_axisangle(full_turn)

        # Constrain the turn based on the size of the time-step.
        frame_max_rad = deg2rad(world.delta_seconds * TURN_SPEED_DEG_PER_SECOND)
        turn_radians = copysign(min(frame_max_rad, abs(turn_radians)),
                                turn_radians)

        this.entity_rot.rot >>= fquat(turn_axis, turn_radians)
    end
end

function make_turn(entity::Entity, turn_move::TurnMove)
    return add_component(entity, CabTurnComponent, turn_move.duration, turn_move.target)
end