termengine.js
v1.0.1
Published
Terminal Game Engine is an optimized, easy-to-use game engine for developing terminal-based games. This engine allows you to quickly create simple 2D games and develop high-performance terminal applications using the terminal-kit package.
Downloads
12
Maintainers
Readme
Terminal Game Engine
Terminal Game Engine is an optimized, easy-to-use game engine for developing terminal-based games. This engine allows you to quickly create simple 2D games and develop high-performance terminal applications using the terminal-kit package.
Features
- 2D terminal graphics support
- Game loop and rendering processes
- Event handler for user input
- Easy collision detection
- Pathfinding algorithms
- CLI tools for project management
Installation
To start your project, simply install the termengine.js
package.
npm install termengine.js
Usage
CLI Commands
Initialize a Project
To start a new game project:
npx termengine.js init my-game
cd my-game
npm install
Compile the Project
To compile the project and generate optimized code:
npx termengine.js compile
Build the Project
To build the project without compiling:
npx termengine.js build
Run the Project
To run the project:
npx termengine.js run
Game Engine Usage
To develop a game using the game engine, follow these steps.
index.js
const { terminal } = require('terminal-kit');
const ScreenBuffer = require('terminal-kit').ScreenBuffer;
const { EventHandler } = require('termengine.js');
const screen = new ScreenBuffer({ dst: terminal, width: terminal.width, height: terminal.height });
const eventHandler = new EventHandler();
let gameState = {
player: {
x: 10,
y: 10,
char: '@',
color: 'green'
},
enemies: []
};
function draw() {
screen.clear();
screen.put({ x: gameState.player.x, y: gameState.player.y, attr: { color: gameState.player.color } }, gameState.player.char);
screen.draw();
}
function update() {
// Update game state
// For example, move enemies, check collisions, etc.
}
function gameLoop() {
setInterval(() => {
update();
draw();
}, 1000 / 30); // 30 FPS
}
function handleInput() {
terminal.grabInput();
terminal.on('key', (name) => {
if (name === 'CTRL_C') {
process.exit();
} else if (name === 'UP') {
gameState.player.y--;
} else if (name === 'DOWN') {
gameState.player.y++;
} else if (name === 'LEFT') {
gameState.player.x--;
} else if (name === 'RIGHT') {
gameState.player.x++;
}
});
}
function startGame() {
draw();
handleInput();
gameLoop();
}
startGame();
Game Engine Structure
src/engine/index.js
The entry point and main game loop of the game engine:
class GameEngine {
constructor() {
this.isRunning = false;
}
start() {
this.isRunning = true;
this.gameLoop();
}
stop() {
this.isRunning = false;
}
gameLoop() {
if (!this.isRunning) return;
this.update();
this.render();
setTimeout(() => this.gameLoop(), 1000 / 30); // 30 FPS
}
update() {
// Update game state
}
render() {
// Render game screen
}
}
module.exports = GameEngine;
src/engine/render.js
Module managing render operations:
const { terminal } = require('terminal-kit');
const ScreenBuffer = require('terminal-kit').ScreenBuffer;
class Renderer {
constructor() {
this.screenBuffer = new ScreenBuffer({ dst: terminal, width: terminal.width, height: terminal.height });
}
draw(gameState) {
this.screenBuffer.clear();
gameState.entities.forEach(entity => {
this.screenBuffer.put({ x: entity.x, y: entity.y, attr: { color: entity.color } }, entity.char);
});
this.screenBuffer.draw();
}
}
module.exports = Renderer;
src/engine/input.js
Module managing user inputs:
const { terminal } = require('terminal-kit');
class InputHandler {
constructor() {
this.keys = {};
terminal.grabInput();
terminal.on('key', (name) => {
this.keys[name] = true;
if (name === 'CTRL_C') {
process.exit();
}
});
}
isKeyPressed(key) {
return !!this.keys[key];
}
clear() {
this.keys = {};
}
}
module.exports = InputHandler;
src/engine/gameState.js
Module managing game state:
class GameState {
constructor() {
this.entities = [];
}
addEntity(entity) {
this.entities.push(entity);
}
removeEntity(entity) {
this.entities = this.entities.filter(e => e !== entity);
}
}
module.exports = GameState;
Utility Functions
src/utils/eventHandler.js
Using the event handler:
class EventHandler {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event, listener) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(l => l !== listener);
}
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(listener => listener(...args));
}
}
module.exports = { EventHandler };
src/utils/collision.js
Collision detection:
function checkCollision(obj1, obj2) {
return obj1.x < obj2.x + obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y + obj2.height &&
obj1.y + obj1.height > obj2.y;
}
module.exports = { checkCollision };
src/utils/pathfinding.js
Pathfinding algorithm:
class Node {
constructor(x, y, parent = null) {
this.x = x;
this.y = y;
this.parent = parent;
this.g = 0;
this.h = 0;
this.f = 0;
}
}
function aStar(grid, start, end) {
let openList = [];
let closedList = [];
let startNode = new Node(start.x, start.y);
let endNode = new Node(end.x, end.y);
openList.push(startNode);
while (openList.length > 0) {
let currentNode = openList.reduce((prev, curr) => (prev.f < curr.f ? prev : curr));
if (currentNode.x === endNode.x && currentNode.y === endNode.y) {
let path = [];
let current = currentNode;
while (current) {
path.push({ x: current.x, y: current.y });
current = current.parent;
}
return path.reverse();
}
openList = openList.filter(node => node !== currentNode);
closedList.push(currentNode);
let neighbors = getNeighbors(grid, currentNode);
for (let neighbor of neighbors) {
if (closedList.find(node => node.x === neighbor.x && node.y === neighbor.y)) {
continue;
}
neighbor.g = currentNode.g + 1;
neighbor.h = Math.abs(neighbor.x - endNode.x) + Math.abs(neighbor.y - endNode.y);
neighbor.f = neighbor.g + neighbor.h;
if (openList.find(node => node.x === neighbor.x && node.y === neighbor.y && node.g <= neighbor.g)) {
continue;
}
openList.push(neighbor);
}
}
return [];
}
function getNeighbors(grid, node) {
let neighbors = [];
let directions = [
{ x: -1, y: 0 },
{ x: 1, y: 0 },
{ x: 0, y: -1 },
{ x: 0, y: 1 },
];
for (let dir of directions) {
let x = node.x + dir.x;
let y = node.y + dir.y;
if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== 1) {
neighbors.push(new Node(x, y, node));
}
}
return neighbors;
}
module.exports = { aStar };
Changelog
Version 1.0.1
- Improved
package.json
with additional keywords for better discoverability - Added delay functions using
delay
package - Optimized existing code for better performance
- Fixed missing or non-functional files to ensure full functionality
- Enhanced flexibility and user control within the game engine
Updated package.json
Keywords changed, version updated.
Added delay functions
src/utils/delay.js
const delay = require('delay');
async function wait(ms) {
await delay(ms);
}
module.exports = { wait };
Integrated delay functions into the engine
src/engine/index.js
const { wait } = require('../utils/delay');
class GameEngine {
constructor() {
this.isRunning = false;
}
async start() {
this.isRunning = true;
await this.gameLoop();
}
stop() {
this.isRunning = false;
}
async gameLoop() {
while (this.isRunning) {
this.update();
this.render();
await wait(1000 / 30); // 30 FPS
}
}
update() {
// Update game state
}
render() {
// Render game screen
}
}
module.exports = GameEngine;
Enhanced flexibility and user control
You can add more utility functions and modules to provide additional features like custom events, improved rendering, more sophisticated input handling, etc. For example:
src/utils/customEvent.js
class CustomEvent {
constructor(type, detail = {}) {
this.type = type;
this.detail = detail;
this.timeStamp = Date.now();
}
}
module.exports = { CustomEvent };
Thanks for using termengine.js