game-platform
v3.1.5
Published
Common utility for Canvas games
Downloads
22
Maintainers
Readme
Roadmap
[ ] Provide some basic SCSS that can be imported
[ ] Add a Game Grid to the common utilities
[ ] Add hit detection against rotated rects/images (currently only leveled rects are supported)
[ ] Create real objects for mouseMoveDataInterface to make the interface easier to document
[ ] Add docs for the canvasAPI as a stand alone
#Changelog
19/12/2021
Release version 3.0.0
- Refactor(CanvasAPI): Rename to Painter (new Painter())
- Feature(Painter): Added event hooks into CanvasAPI allowing it to receive an initial canvas
02/08/2021
Release version 2.1.1
- Update all dependencies to latest
31/07/2021
Release version 2.1.0
- Refactor(build): Remove JARB and rollup dependency
- Fix(General): Fix all implicit any issues with typescript
- Breaking(CanvasAPI): Change function name from drawFn to render when adding a generic shape (canvasAPI.addShape).
31/07/2021
Release version 2.0.0
- Change build to rollup (Starting migration away from jarb)
14/11/2019
Release version 0.4.0:
- Added click detection against all layers
- Clicking on the Canvas will now return an array of objects with ID and layerName
- Added color param for addCircle()
- Fixed bugs related to click-hit not working
###29/10/2019 Release version 0.3.1:
- Moved the RAF in Engine, now RAF will be queued first before an system runs, this will enable systems to cancel the frame ###29/10/2019 Release version 0.3.0:
- Moved all layerName arguments into the objects for all functions (for functions that accept Objects)
- Added a drawTextBubble() text function
- Added click detection(for click and select box) against rect shapes (it was only circles)\
- Added tests for click detection against rect\
- onViewMapMove/click and onMinimapMove/click are now optional callbacks\
- Add hit detection against Images\
- Add configuration to disable selection box (options.enableSelectBox = false)\
- Added documentation to how writeBubble works\
- Updated docs that hit detection only works on 'initial' layer\
###24/10/2019 Release version 0.2.1
- Added the addArc() method which enables drawing arcs.
###22/10/2019 Release version 0.2.0
- Added an Engine class to encapsulate rAF loop (start, stop, addSystem).
###17/10/2019 Release version 0.1.2
- Added the ability to remove a layer (canvasAPI.removeLayer(name))
###03/10/2019 Release version 0.1.1
- Added onMiniMapClick and onMiniMapView
###03/10/2019 Release version 0.1.0
- Added support for Layers.
- To use layers:
- the canvas element must be positioned in a container element.
- The container element must be positioned absolute with a set height and width
- The canvas elements must be positioned absolute (To enable layering)
###02/10/2019 Release version 0.0.9
- Updated JARB to 3.0.6
- Updated Canvas to 2.6.0 and removed canvas prebuilt.
- These changes now enable support for node 12.x
###15/02/2019###
Release version 0.0.8
- Updated JARB to 2.0.0-beta.2
###09/01/2019 Release version 0.0.7
- Fix ObjectPool.generate(amount) to generate UP TO $amount of free objects
###06/01/2019
- Updated jarb to 1.0.8
###27/12/2018 Release version 0.0.6
- Ensure React is not bundled in the dist file
- Document all exported properties
- Remove ALL TODOs and REFACTORS
#Features
- Engine
- CanvasAPI that can be initialized with 2d ctx
- (React) Game Canvas (Both Main Map and Mini Map) with click detection on what shape was clicked
- ECS Entity
- Utility to loop over entities
- Object Pool utility to pre-create and manage instances of objects
Usage - Engine
import {Engine} from 'game-platform/dist';
let engine = new Engine();
engine.addSystem((systemArguments) => {
// run any system you want, order specified by order of insertion
});
engine.addSystem((systemArguments) => {
// run any system you want, order specified by order of insertion
});
let systemArguments = {
// object that is passed to all systems
// alternatively it can also be a function that returns the object
// in case of a function, it's evaluated every frame, so keep it light!
}
engine.run(systemArguments); // runs in a loop
engine.stop(); // stops the loop completely
Usage - Game Canvas
import {render} from 'react-dom';
import 'index.scss';
import GameCanvas from 'lib/GameCanvas/GameCanvas';
let gameCanvas = new GameCanvas({
// The mapHeight/width determine the size of the map,
// while viewHeight/width determine the size visible on the screen.
// these are not CSS attributes, but rather canvas html properties
// The library uses viewHeight for the main view, and mapWidth for the minimap
mapHeight: 4000, // in pixels
mapWidth : 4000, // in pixels
viewHeight : 400, // this is the what you actually see in the canvas
viewWidth : 400, // this is the what you actually see in the canvas
selectedBoxColor: 'blue', // the color for the current selection box of the user
enableSelectBox: true, // defaults to true, can be set to false to disable the click->drag select box
onViewMapClick : (...args) => {
let clickDataInterface = {
// The library only detects hits against circles, rects and images, it ignores all other shapes
// rects and images are calculated without orientation (rotation)
// you can implement your own click detection system
hits: [], // array of shape IDs that were clicked on
dbClick: true, // BOOL, true or false
isMouseDown: true, // BOOL, true or false
// Information about the currently selected area (when you select an area with your mouse)
// if mouse is not held down(no selection), these numbers will be set to 0
selectedBox: {
start: {
x:10,
y:0
},
end: {
x:110,
y:75
},
height: 75,
width: 100
}
};
},
onViewMapMove : (mouseMoveData) => {
let mouseMoveDataInterface = {
dbClick: true, // BOOL, true or false
isMouseDown: true, // BOOL, true or false
// Information about the currently selected area (when you select an area with your mouse)
// if mouse is not held down(no selection), these numbers will be set to 0
selectedBox: {
start: {
x:10,
y:0
},
end: {
x:110,
y:75
},
height: 75,
width: 100
}
};
}
});
let apis = {};
// these functions return React Element instances
// their callback provides a way to access the internal Canvas API
let mainMap = gameCanvas.generateMapCanvas((API) => {
apis.main = API;
});
let miniMap = gameCanvas.generateMiniMapCanvas((API) => {
apis.mini = API;
});
// Unfortunate, as we can only expose the APIs once the canvas context exists
// but we also can't have the canvas context until we render
// We render our new canvas react elements with React
render(<div>
{mainMap}
{miniMap}
</div>, document.getElementById('app'), () => {
apis.main.addRect({
id: 'SomeRect', // Must be Unique, as a Map is used internally
x: 10,
y: 15,
width: 20,
height: 35,
strokeStyle: 'blue'
});
apis.main.addCircle({
id: 'ExampleID',
x:50,
y: 50,
radius: 15,
strokeStyle: 'red'
});
let img = new Image;
img.src = 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png';
img.onload = () => {
apis.main.drawImage({
id : 'my-image',
image:img, // the image to display
x: 100, y: 100, // pos for x,y..
height: 100, width: 100,
cropStartX:0, cropStartY:0, cropSizeX:img.width, cropSizeY:img.height,
rotation : 0.2 // in radians
});
};
apis.main.drawTextBubble({
id: 'bubbleTextExampleID',
text: 'It is dangerous to go alone! \ntake this!',
backgroundColor: 'green',
borderColor:'orange',
borderWidth: 3,
fontColor: 'purple',
x: 200,
y: 200,
height:0, // the minimum value is the text value within
width:0, // the minimum value is the text value within
fontSize: 16
});
// in a game, you usually render in a loop, the API supports it by having deterministic draws
setInterval(() => {
// Once we have all our shapes in place, we use the internal Painter to draw them
// Internally this deletes the entire canvas, and re-renders all the shapes.
apis.main.drawAllShapesInLayer();
apis.mini.drawAllShapesInLayer();
}, 16);
});
Usage - ECS
import Entity from 'lib/ECS/Entity';
it('Creates a new entity', () => {
let e = new Entity();
expect(e.id).not.toBeUndefined();
expect(e.components).toEqual({});
});
it('Adds and removes components', () => {
let e = new Entity();
let comp = {name:'test', foo:'bar'};
e.addComponent(comp);
expect(e.components.test).toBe(comp);
e.removeComponent(comp);
expect(e.components.test).toBeUndefined();
});
it('Tests the hasComponent method', () => {
let e = new Entity();
let comp1 = {name:'test1', foo:'bar'};
e.addComponent(comp1);
let comp2 = {name:'test2', foo:'bar'};
e.addComponent(comp2);
expect(e.hasComponents('test1')).toBe(true);
expect(e.hasComponents(['test1'])).toBe(true);
expect(e.hasComponents(['test1', 'test2'])).toBe(true);
expect(e.hasComponents(['anotherComp'])).toBe(false);
expect(e.hasComponents('anotherComp')).toBe(false);
expect(e.hasComponents()).toBe(true);
});
it('Entity can destroy itself', () => {
let e = new Entity();
let comp1 = {name:'test1', foo:'bar'};
e.addComponent(comp1);
// always returns a collection
expect(Entity.getByComps(['test1'])[0]).toBe(e);
e.destroy();
expect(Entity.entities[e.id]).toBeUndefined();
let resp = Entity.getByComps(['test1']);
expect(resp.length).toBe(0);
});
Usage - The EntityLoop
import entityLoop from 'lib/ECS/util/entityLoop';
import Entity from 'lib/ECS/Entity';
it ('Loops on Entity arrays', () => {
let foo = new Entity();
foo.addComponent({
name : 'lol'
});
let runs = 0;
let ents = entityLoop(Entity.getByComps('lol'), (ent) => {
runs++;
expect(ent).not.toBeUndefined();
return true;
});
expect(ents.length).toBe(1);
expect(runs).toBe(1);
});
Usage - The Object Pool
import ObjectPool from 'lib/ObjectPool/ObjectPool';
// Define what type of pool you'd like to make
beforeEach(() => {
class Foo {
constructor() {
this.isAlive = true;
}
}
// if you try to acquire an object when the pool is empty, this will create more instances on the fly
let incrementWhenEmpty = 100;
pool = new ObjectPool(Foo, incrementWhenEmpty);
});
it('Acquires an object instance', () => {
pool.generate(100);
let obj = pool.acquire();
expect(obj.isAlive).toBe(true);
expect(pool.stats.free).toBe(99);
});
// the pool exposes the following stats
pool.stats = {
free: 0,
used: 0,
total: 0
};