game_space_chunk_chunk.js
/**
* @module Chunk
* @fileOverview Contains Chunk class.
*/
/**
* @class
* Individual chunk for space partitioning.
*/
class Chunk {
/**
* Create a new chunk.
* @param {Space} space Parent space
* @param {number} x Index of chunk along x-axis
* @param {number} y Index of chunk along y-axis
* @constructor
*/
constructor(space, x, y) {
this._space = space;
this._x = x;
this._y = y;
this.actors = new Map();
this._nextActorID = 0;
this._overlappingActors = new Set();
this.units = [];
this._persistent = false;
this.cachedUnits = null;
this.makeUnits();
}
//
// Getters
//
/**
* Get the parent space.
* @returns {Space} Parent space
*/
get space() {
return this._space;
}
/**
* Get the index along the x-axis.
* @returns {number} Chunk x index
*/
get x() {
return this._x;
}
/**
* Get the index along the y-axis
* @returns {number} Chunk y index
*/
get y() {
return this._y;
}
/**
* Check if the chunk, or any of its actors, are persistent.
* @returns {boolean} Whether chunk is persistent
*/
get persistent() {
if (this._persistent)
return true;
for (const actor of this.actors.values())
if (actor.persistent)
return true;
return false;
}
/**
* Find and set the next actor ID to use.
*/
getNextID() {
while (this.actors.has(this._nextActorID))
this._nextActorID++;
}
//
// Setters
//
/**
* Set the chunk itself to be persistent.
* @param {boolean} val New persistence value
*/
set persistent(val) {
this._persistent = val;
}
//
// Update & Draw
//
/**
* Update the chunk and its actors.
* @param {number} elapsed Time since last update cycle in seconds
*/
update(elapsed) {
for (const actor of this.actors.values())
actor.update(elapsed);
this._checkActors();
}
/**
* Draw all units within the chunk.
* @param {CanvasRenderingContext2D} ctx Canvas context to draw on
*/
drawUnits(ctx) {
const size = this.space.chunkSize * this.space.unitSize;
ctx.save();
ctx.translate(this.x * size, this.y * size);
if (this.cachedUnits !== null) {
ctx.drawImage(this.cachedUnits, 0, 0, size + 1, size + 1);
return;
}
for (let x = 0; x < this.space.chunkSize; x++) {
for (let y = 0; y < this.space.chunkSize; y++) {
const unit = this.units[x][y];
if (unit !== null)
unit.draw(ctx);
}
}
ctx.restore();
}
/**
* Draw all actors in the chunk.
* @param {CanvasRenderingContext2D} ctx Canvas context to draw on
*/
drawActors(ctx) {
const top = [];
for (const actor of this.actors.values()) {
if (actor.alwaysOnTop)
top.push(actor);
else if (!actor.hasParent())
actor.draw(ctx);
}
for (const actor of top)
actor.draw(ctx);
}
/**
* Draw all debugging information for the chunk and its actors.
* @param {CanvasRenderingContext2D} ctx Canvas context to draw on
*/
drawDebug(ctx) {
if ($$.debug.chunks) {
ctx.fillStyle = $$.colors.debug;
ctx.strokeStyle = $$.colors.debug;
const zoom = this.space.camera.calcZoom();
ctx.lineWidth = 2 / zoom;
const s = this.space.chunkSize * this.space.unitSize;
const x = this.x * s;
const y = this.y * s;
ctx.strokeRect(x, y, s, s);
if (this.space.camera !== null) {
if (this.space.chunks.getChunkAt(this.space.camera.pos) === this) {
ctx.fillStyle = $$.colors.debug2;
ctx.strokeStyle = $$.colors.debug2;
ctx.strokeRect(x + 2, y + 2, s - 4, s - 4);
}
}
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
ctx.shadowColor = "rgba(0,0,0,0.32)";
if (this.space.camera.zoom > -3) {
ctx.save();
ctx.translate(x, y);
ctx.scale(1 / zoom, 1 / zoom);
ctx.textAlign = "left";
ctx.font = "bold 20px " + $$.fonts.debug;
const id = this.x + ", " + this.y;
ctx.fillText(id, 8, 20);
ctx.font = "16px " + $$.fonts.debug;
let text = this.actors.size + " actors";
if (this.actors.size === 1)
text = this.actors.size + " actor";
ctx.fillText(text, 8, 40);
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowColor = 'transparent';
ctx.restore();
}
}
for (const actor of this.actors.values())
actor.drawDebug(ctx);
}
//
// Actor Management
//
/**
* Add an actor to the chunk.
* @param {Actor} actor Actor to add
*/
addActor(actor) {
this.actors.set(this._nextActorID, actor);
actor.id = this._nextActorID;
actor.space = this.space;
this.getNextID();
if (!actor.initialized)
actor.init();
}
/**
* Remove an actor from the chunk.
* @param {number} id Chunk identifier of actor
* @private
*/
_removeActor(id) {
if (this.actors.has(id)) {
this.actors.delete(id);
if (id < this._nextActorID)
this._nextActorID = id;
}
}
/**
* Check collision for an actor with other actors.
* @param {Actor} actor Actor to check collision for
* @param {Vec2} pos Position to attempt to move to
* @returns {Object} Object containing collision result data
*/
checkCollision(actor, pos) {
for (const other of this._overlappingActors.values()) {
if (other === actor)
continue;
if (other.markedForDeletion)
continue;
for (const comp of actor.components.getCollisions()) {
const result = other.checkCollision(comp, pos);
if (result.result) {
return result;
}
}
}
return {
result: false
};
}
/**
* Get all actors near a specified position and within a radius.
* @param {Actor} ignore Actor to ignore
* @param {Vec2} pos Point to find actors around
* @param {number} radius Radius to find actors within
* @returns {Actor[]} Array of actors near point within at radius
*/
getNearbyActors(ignore, pos, radius) {
const result = [];
for (const actor of this.actors.values())
if (actor !== ignore && pos.dist(actor.pos) <= radius)
result.push(actor);
return result;
}
/**
* Transfer an actor to another chunk.
* @param {Actor} actor Actor to transfer
* @param {Chunk} to Chunk to transfer actor to
* @private
*/
_transferActor(actor, to) {
this._removeActor(actor.id);
actor.id = null;
to.addActor(actor);
}
/**
* Check the status of all actors within the chunk.
* This includes checking if they are marked for deletion,
* and if they should be moved to a different chunk.
* @private
*/
_checkActors() {
for (const actor of this.actors.values()) {
if (actor.markedForDeletion) {
if (actor.id < this._nextActorID)
this._nextActorID = actor.id;
this.actors.delete(actor.id);
continue;
}
const c = this.space.chunks.valChunkAt(actor.globalPos);
if (c !== this)
this._transferActor(actor, c);
}
}
/**
* Add an actor that is overlapping the chunk.
* @param {Actor} actor Actor to add as overlapping
*/
addOverlapping(actor) {
this._overlappingActors.add(actor);
}
/**
* Remove an actor from overlapping with the chunk.
* @param {Actor} actor Actor to remove as overlapping
*/
removeOverlapping(actor) {
this._overlappingActors.delete(actor);
}
//
// Units
//
/**
* Generate units within the chunk.
*/
makeUnits() {
for (let x = 0; x < this.space.chunkSize; x++) {
this.units.push([]);
for (let y = 0; y < this.space.chunkSize; y++)
this.units[x].push(null);
}
}
/**
* Get a specific unit in the chunk.
* @param {number} x Relative index along the x-axis
* @param {number} y Relative index along the y-axis
* @returns {ChunkUnit|null} Chunk unit at specified position, or null if there is none.
*/
getUnit(x, y) {
if (x < 0 || x >= this.space.chunkSize ||
y < 0 || y >= this.space.chunkSize)
return null;
return this.units[Math.floor(x)][Math.floor(y)];
}
/**
* Get a chunk unit at a specific position in the space.
* @param {number} x Global position along the x-axis
* @param {number} y Global position along the y-axis
* @returns {ChunkUnit|null} Chunk unit at specified position, or null if there is none.
*/
getUnitAt(x, y) {
const size = this.space.unitSize * this.space.chunkSize;
x -= this.x * size;
y -= this.y * size;
x = Math.floor(x / this.space.unitSize);
y = Math.floor(y / this.space.unitSize);
if (x < 0 || x >= this.space.chunkSize ||
y < 0 || y >= this.space.chunkSize)
return null;
return this.units[x][y];
}
/**
* Set a unit at a specific index.
* @param {number} x Relative index along the x-axis
* @param {number} y Relative index along the y-axis
* @param {ChunkUnit|null} unit Unit to set at indices, or null to remove.
*/
setUnit(x, y, unit) {
if (x < 0 || x >= this.space.chunkSize ||
y < 0 || y >= this.space.chunkSize)
return;
this.units[x][y] = unit;
if (unit === null)
return;
unit.chunk = this;
const ox = this.x * this.space.unitSize * this.space.chunkSize;
const oy = this.y * this.space.unitSize * this.space.chunkSize;
unit.x = x * this.space.unitSize + ox;
unit.y = y * this.space.unitSize + oy;
}
}
export default Chunk;