game_actor_moving_actor.js

import Actor from "./actor.js";
import Vec2 from "../vector/vec2.js";

/**
 * @module MovingActor
 * @fileoverview Contains MovingActor class.
 */

/**
 * @class
 * @extends Actor
 * Base class for actors that move, whether controlled by the user or by pathing.
 */
class MovingActor extends Actor {
    /**
     * Create a new moving actor.
     * @param {Vec2} pos Initial position of actor
     * @param {number} Initial rotation of actor
     * @param {number|Vec2} Initial scale of actor
     * @param {string|Sprite|null} sprite Sprite for visual rendering
     * @param {"auto"|null} collision Collision initializer (optional)
     * @param {string|Controller|null} controller Controller for user movement (optional)
     * @param {number} speed Speed of movement (300 by default)
     * @constructor
     */
    constructor({
        pos = new Vec2(),
        rotation = 0,
        scale = 4,
        sprite = null,
        collision = null,
        controller = null,
        speed = 300,
    } = {}) {
        super({
            pos: pos,
            rotation: rotation,
            scale: scale,
            collision: collision,
            sprite: sprite
        });

        this.move = new Vec2(0, 0);
        this.speed = speed;
        this.speedMultiplier = 1;

        this.controller = null;
        if (controller !== null)
            this.setController(controller);
    }

    update(elapsed) {
        if (this.controller != null) {
            this.move = new Vec2(0, 0);
            this.speedMultiplier = 1;
            this.controller.update(this, elapsed);
        }

        this.doMoveIter(elapsed);

        super.update(elapsed);
    }

    /**
     * Set the controller of the moving actor.
     * @param {string} id ID of the controller type to use
     */
    setController(id) {
        const controller = $$.reg.controller.get(id);
        if (controller !== null)
            this.controller = controller;
    }

    /**
     * Perform multiple movements in a single frame. Helps with accuracy.
     * @param {number} elapsed Time since last update cycle in seconds
     */
    doMoveIter(elapsed) {
        const n = 16;

        for (let i = 0; i < n; i++)
            this.doMove(this.move.copy(), elapsed / n);
    }

    /**
     * Perform the movement controlled by a controller.
     * @param {Vec2} move Movement vector
     * @param {number} elapsed Time since last update
     */
    doMove(move, elapsed) {
        if (move.x !== 0 && move.y !== 0)
            move = move.norm();

        let speed = this.speedMultiplier;
        const unit = this.space.getUnitAt(this.pos.x, this.pos.y);
        if (unit !== null)
            if (!unit.canWalkOn)
                speed = 0.4;

        move = move.mulv(this.speed * speed);

        if (move.x !== 0 || move.y !== 0) {
            const to = this.pos.add(move.mulv(elapsed));

            let collision = {
                result: false
            };

            let result = this.space.checkUnitCollision(this, to);

            if (!result.result) {
                for (const chunk of this.overlappingChunks.values()) {
                    result = chunk.checkCollision(this, to);
                    if (result.result)
                        collision = result;
                }
            } else {
                collision = result;
            }

            if (collision.result) {
                if (collision.axes.x !== 0)
                    this._pos.x = to.x - Math.sign(move.x) * (collision.overlap + 1) * Math.abs(collision.axes.x);
                else
                    this._pos.x = to.x;

                if (collision.axes.y !== 0)
                    this._pos.y = to.y - Math.sign(move.y) * (collision.overlap + 1) * Math.abs(collision.axes.y);
                else
                    this._pos.y = to.y;
            } else {
                this.pos = to.copy();
            }
        }
    }
}

export default MovingActor;