parent
8b0f6f34a9
commit
a0bace91a0
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,6 +10,9 @@ dist/*
|
||||||
# Include the built-in asset folder
|
# Include the built-in asset folder
|
||||||
!dist/builtin/
|
!dist/builtin/
|
||||||
|
|
||||||
|
# Include the hw1 assets
|
||||||
|
!dist/hw3_assets/
|
||||||
|
|
||||||
# Include the hw1 assets
|
# Include the hw1 assets
|
||||||
!dist/hw4_assets/
|
!dist/hw4_assets/
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Actor from "../DataTypes/Interfaces/Actor";
|
import Actor from "../DataTypes/Interfaces/Actor";
|
||||||
import Updateable from "../DataTypes/Interfaces/Updateable";
|
import Updateable from "../DataTypes/Interfaces/Updateable";
|
||||||
import AI from "../DataTypes/Interfaces/AI";
|
import AI from "../DataTypes/Interfaces/AI";
|
||||||
|
import GoapAI from "../DataTypes/Interfaces/GoapAI"
|
||||||
import Map from "../DataTypes/Map";
|
import Map from "../DataTypes/Map";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,7 +40,7 @@ export default class AIManager implements Updateable {
|
||||||
* @param name The name of the AI to register
|
* @param name The name of the AI to register
|
||||||
* @param constr The constructor for the AI
|
* @param constr The constructor for the AI
|
||||||
*/
|
*/
|
||||||
registerAI(name: string, constr: new <T extends AI>() => T ): void {
|
registerAI(name: string, constr: new <T extends AI | GoapAI>() => T ): void {
|
||||||
this.registeredAI.add(name, constr);
|
this.registeredAI.add(name, constr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export default class AIManager implements Updateable {
|
||||||
* @param name The name of the AI to add
|
* @param name The name of the AI to add
|
||||||
* @returns A new AI instance
|
* @returns A new AI instance
|
||||||
*/
|
*/
|
||||||
generateAI(name: string): AI {
|
generateAI(name: string): AI | GoapAI {
|
||||||
if(this.registeredAI.has(name)){
|
if(this.registeredAI.has(name)){
|
||||||
return new (this.registeredAI.get(name))();
|
return new (this.registeredAI.get(name))();
|
||||||
} else {
|
} else {
|
||||||
|
|
82
src/Wolfie2D/AI/GoapActionPlanner.ts
Normal file
82
src/Wolfie2D/AI/GoapActionPlanner.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import Graph from "../DataTypes/Graphs/Graph";
|
||||||
|
import GoapAction from "../DataTypes/Interfaces/GoapAction";
|
||||||
|
import GoapAI from "../DataTypes/Interfaces/GoapAI";
|
||||||
|
import Queue from "../DataTypes/Queue";
|
||||||
|
import Stack from "../DataTypes/Stack";
|
||||||
|
import GraphUtils from "../Utils/GraphUtils";
|
||||||
|
|
||||||
|
export default class GoapActionPlanner {
|
||||||
|
mapping: Map<number,GoapAction | string>;
|
||||||
|
graph: Graph;
|
||||||
|
path: Array<number>;
|
||||||
|
|
||||||
|
plan(goal: string, possibleActions: Array<GoapAction>, currentStatus: Array<string>, actor: GoapAI): Stack<GoapAction> {
|
||||||
|
this.graph = new Graph(true);
|
||||||
|
this.mapping = new Map();
|
||||||
|
|
||||||
|
//0 is our root
|
||||||
|
this.graph.addNode();
|
||||||
|
this.mapping.set(0,"Start");
|
||||||
|
//1 is the goal
|
||||||
|
this.graph.addNode();
|
||||||
|
this.mapping.set(1,"Goal");
|
||||||
|
this.graph.addEdge(1,1,Number.POSITIVE_INFINITY);
|
||||||
|
|
||||||
|
//Build tree from 0 to 1
|
||||||
|
this.buildTree(0, goal, possibleActions, currentStatus);
|
||||||
|
console.log(this.graph.toString());
|
||||||
|
|
||||||
|
//Run djikstra to find shortest path
|
||||||
|
this.path = GraphUtils.djikstra(this.graph, 0);
|
||||||
|
|
||||||
|
//Push all elements of the plan
|
||||||
|
let plan = new Stack<GoapAction>();
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
while(this.path[i] !== -1){
|
||||||
|
console.log(this.path[i]);
|
||||||
|
if (this.path[i] !== 0){
|
||||||
|
plan.push(<GoapAction>this.mapping.get(this.path[i]));
|
||||||
|
}
|
||||||
|
i = this.path[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTree(root: number, goal:string, possibleActions: Array<GoapAction>, currentStatus: Array<string>): void {
|
||||||
|
//For each possible action
|
||||||
|
possibleActions.forEach(action => {
|
||||||
|
console.log("root:" + root + ",action precons:" + action.preconditions.toString()
|
||||||
|
+ ", action effects:" + action.effects.toString() + ", current Status:" + currentStatus.toString())
|
||||||
|
|
||||||
|
//Can it be performed?
|
||||||
|
if (action.checkPreconditions(currentStatus)){
|
||||||
|
//This action can be performed
|
||||||
|
//Add effects to currentStatus
|
||||||
|
let newStatus = [...currentStatus];
|
||||||
|
newStatus.push(...action.effects);
|
||||||
|
|
||||||
|
//Check if the new node is the goal
|
||||||
|
if (newStatus.includes(goal)){
|
||||||
|
console.log("AT GOAL");
|
||||||
|
let newNode = this.graph.addNode() - 1;
|
||||||
|
this.mapping.set(newNode, action);
|
||||||
|
this.graph.addEdge(root, newNode, action.cost);
|
||||||
|
this.graph.addEdge(newNode, 1, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add node and edge from root
|
||||||
|
let newNode = this.graph.addNode() - 1;
|
||||||
|
this.mapping.set(newNode, action);
|
||||||
|
this.graph.addEdge(root, newNode, action.cost);
|
||||||
|
|
||||||
|
//Recursive call
|
||||||
|
console.log(possibleActions.indexOf(action))
|
||||||
|
let newActions = possibleActions.filter(act => act !== action)
|
||||||
|
this.buildTree(newNode, goal, newActions, action.effects);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
40
src/Wolfie2D/AI/StateMachineGoapAI.ts
Normal file
40
src/Wolfie2D/AI/StateMachineGoapAI.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import GoapAction from "../DataTypes/Interfaces/GoapAction";
|
||||||
|
import GoapAI from "../DataTypes/Interfaces/GoapAI";
|
||||||
|
import Queue from "../DataTypes/Queue";
|
||||||
|
import Stack from "../DataTypes/Stack";
|
||||||
|
import StateMachine from "../DataTypes/State/StateMachine";
|
||||||
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
import GoapActionPlanner from "./GoapActionPlanner";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of a @reference[StateMachine] that is configured to work as an AI controller for a @reference[GameNode]
|
||||||
|
*/
|
||||||
|
export default class StateMachineGoapAI extends StateMachine implements GoapAI {
|
||||||
|
/** The GameNode that uses this StateMachine for its AI */
|
||||||
|
protected owner: GameNode;
|
||||||
|
|
||||||
|
goal: string;
|
||||||
|
|
||||||
|
currentStatus: Array<string>;
|
||||||
|
|
||||||
|
possibleActions: Array<GoapAction>;
|
||||||
|
|
||||||
|
plan: Stack<GoapAction>;
|
||||||
|
|
||||||
|
planner: GoapActionPlanner;
|
||||||
|
|
||||||
|
// @implemented
|
||||||
|
initializeAI(owner: GameNode, config: Record<string, any>): void {}
|
||||||
|
|
||||||
|
// @implemented
|
||||||
|
destroy(){
|
||||||
|
// Get rid of our reference to the owner
|
||||||
|
delete this.owner;
|
||||||
|
this.receiver.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @implemented
|
||||||
|
activate(options: Record<string, any>): void {}
|
||||||
|
|
||||||
|
changeGoal(goal: string): void {}
|
||||||
|
}
|
|
@ -125,7 +125,7 @@ export default class Graph {
|
||||||
for(let i = 0; i < this.numVertices; i++){
|
for(let i = 0; i < this.numVertices; i++){
|
||||||
let edge = this.edges[i];
|
let edge = this.edges[i];
|
||||||
let edgeStr = "";
|
let edgeStr = "";
|
||||||
while(edge !== null){
|
while(edge !== undefined && edge !== null){
|
||||||
edgeStr += edge.y.toString();
|
edgeStr += edge.y.toString();
|
||||||
if(this.weighted){
|
if(this.weighted){
|
||||||
edgeStr += " (" + edge.weight + ")";
|
edgeStr += " (" + edge.weight + ")";
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import NavigationPath from "../../Pathfinding/NavigationPath";
|
import NavigationPath from "../../Pathfinding/NavigationPath";
|
||||||
import AI from "./AI";
|
import AI from "./AI";
|
||||||
|
import GoapAI from "./GoapAI";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A game object that has an AI and can perform its own actions every update cycle
|
* A game object that has an AI and can perform its own actions every update cycle
|
||||||
*/
|
*/
|
||||||
export default interface Actor {
|
export default interface Actor {
|
||||||
/** The AI of the actor */
|
/** The AI of the actor */
|
||||||
ai: AI;
|
ai: AI | GoapAI;
|
||||||
|
|
||||||
/** The activity status of the actor */
|
/** The activity status of the actor */
|
||||||
aiActive: boolean;
|
aiActive: boolean;
|
||||||
|
@ -21,8 +22,9 @@ export default interface Actor {
|
||||||
* Adds an AI to this Actor.
|
* Adds an AI to this Actor.
|
||||||
* @param ai The name of the AI, or the actual AI, to add to the Actor.
|
* @param ai The name of the AI, or the actual AI, to add to the Actor.
|
||||||
* @param options The options to give to the AI for initialization.
|
* @param options The options to give to the AI for initialization.
|
||||||
|
* @param type The type of the AI, 0 for AI, 1 for GoapAI, defaults to assume AI
|
||||||
*/
|
*/
|
||||||
addAI<T extends AI>(ai: string | (new () => T), options: Record<string, any>): void;
|
addAI<T extends AI>(ai: string | (new () => T), options: Record<string, any>, type?: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the AI to start/stop for this Actor.
|
* Sets the AI to start/stop for this Actor.
|
||||||
|
|
42
src/Wolfie2D/DataTypes/Interfaces/GoapAI.ts
Normal file
42
src/Wolfie2D/DataTypes/Interfaces/GoapAI.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import GoapActionPlanner from "../../AI/GoapActionPlanner";
|
||||||
|
import GameEvent from "../../Events/GameEvent";
|
||||||
|
import GameNode from "../../Nodes/GameNode";
|
||||||
|
import Queue from "../Queue";
|
||||||
|
import Stack from "../Stack";
|
||||||
|
import GoapAction from "./GoapAction";
|
||||||
|
import Updateable from "./Updateable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a controller for a bot or a human. Must be able to update
|
||||||
|
*/
|
||||||
|
export default interface GoapAI extends Updateable {
|
||||||
|
/** Current goal of the AI */
|
||||||
|
goal: string;
|
||||||
|
|
||||||
|
/** All current statuses this AI has */
|
||||||
|
currentStatus: Array<string>;
|
||||||
|
|
||||||
|
/** All possible actions that can be carried out */
|
||||||
|
possibleActions: Array<GoapAction>;
|
||||||
|
|
||||||
|
/** Current actions to be carried out */
|
||||||
|
plan: Stack<GoapAction>;
|
||||||
|
|
||||||
|
/** Once we have no actions, the planner can be called to find a new sequence of actions */
|
||||||
|
planner: GoapActionPlanner;
|
||||||
|
|
||||||
|
/** Clears references from to the owner */
|
||||||
|
destroy(): void;
|
||||||
|
|
||||||
|
/** Activates this AI from a stopped state and allows variables to be passed in */
|
||||||
|
activate(options: Record<string, any>): void;
|
||||||
|
|
||||||
|
/** Handles events from the Actor */
|
||||||
|
handleEvent(event: GameEvent): void;
|
||||||
|
|
||||||
|
/** Initializes the AI with the actor and any additional config */
|
||||||
|
initializeAI(owner:GameNode, options: Record<string, any>): void
|
||||||
|
|
||||||
|
/** Change the goal to a new goal */
|
||||||
|
changeGoal(goal: string): void
|
||||||
|
}
|
61
src/Wolfie2D/DataTypes/Interfaces/GoapAction.ts
Normal file
61
src/Wolfie2D/DataTypes/Interfaces/GoapAction.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import StateMachineGoapAI from "../../AI/StateMachineGoapAI";
|
||||||
|
|
||||||
|
export default abstract class GoapAction {
|
||||||
|
/** Cost it takes to complete this action */
|
||||||
|
cost: number;
|
||||||
|
|
||||||
|
/** Preconditions that have to be satisfied for an action to happen */
|
||||||
|
preconditions: Array<string>;
|
||||||
|
|
||||||
|
/** Resulting statuses after this action completes */
|
||||||
|
effects: Array<string>;
|
||||||
|
|
||||||
|
/** If the action fails, do we keep trying until we succeed */
|
||||||
|
loopAction: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to perform an action, if successful, it will return an array of the expected effects, otherwise it will return null
|
||||||
|
* @param statuses Current statuses of the actor
|
||||||
|
* @param actor GameNode for the actor
|
||||||
|
* @param deltaT The time sine the last update
|
||||||
|
* @param target GameNode for a optional target
|
||||||
|
*/
|
||||||
|
abstract performAction(statuses: Array<string>, actor: StateMachineGoapAI, deltaT: number, target?: StateMachineGoapAI): Array<string>;
|
||||||
|
|
||||||
|
/** Check preconditions with current statuses to see if action can be performed */
|
||||||
|
checkPreconditions(statuses: Array<string>): boolean {
|
||||||
|
// Check that every element in the preconditions array is found in the statuses array
|
||||||
|
return (this.preconditions.every((status) => {
|
||||||
|
if (!statuses.includes(status)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add one or more preconditions to this action */
|
||||||
|
addPrecondition(preconditions: string | string[]): void {
|
||||||
|
this.preconditions.push(...preconditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add one or more effects to this action */
|
||||||
|
addEffect(effects: string | string[]): void {
|
||||||
|
this.effects.push(...effects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes an precondition, returns true if successful */
|
||||||
|
removePrecondition(precondition: string): boolean {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes an precondition, returns true if successful */
|
||||||
|
removeEffect(effect: string): boolean {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the cost of this action based on options */
|
||||||
|
abstract updateCost(options: Record<string,number>): void;
|
||||||
|
|
||||||
|
abstract toString(): string;
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ export default interface Navigable {
|
||||||
* Gets a new navigation path based on this Navigable object.
|
* Gets a new navigation path based on this Navigable object.
|
||||||
* @param fromPosition The position to start navigation from.
|
* @param fromPosition The position to start navigation from.
|
||||||
* @param toPosition The position to navigate to.
|
* @param toPosition The position to navigate to.
|
||||||
|
* @param direct If true, move directly from fromPosition to toPosition
|
||||||
*/
|
*/
|
||||||
getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath;
|
getNavigationPath(fromPosition: Vec2, toPosition: Vec2, direct?: boolean): NavigationPath;
|
||||||
}
|
}
|
38
src/Wolfie2D/Events/BattleSystem.ts
Normal file
38
src/Wolfie2D/Events/BattleSystem.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import Updateable from "../DataTypes/Interfaces/Updateable";
|
||||||
|
import EventQueue from "./EventQueue";
|
||||||
|
import Receiver from "./Receiver";
|
||||||
|
|
||||||
|
export default abstract class BattleSystem implements Updateable {
|
||||||
|
|
||||||
|
units: Map<number,Record<string, number>>;
|
||||||
|
receiver: Receiver;
|
||||||
|
eventQueue: EventQueue;
|
||||||
|
statSystem: Array<string>;
|
||||||
|
|
||||||
|
constructor(battleEvents: Array<string>, statSystem: Array<string>){
|
||||||
|
this.eventQueue = EventQueue.getInstance();
|
||||||
|
this.eventQueue.subscribe(this.receiver, battleEvents);
|
||||||
|
this.statSystem = statSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnitStats(id: number): Record<string,number> {
|
||||||
|
return this.units.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateStats(stats: Record<string,number>): Record<string,number> {
|
||||||
|
this.statSystem.forEach(e => {
|
||||||
|
if (stats[e] === undefined){
|
||||||
|
stats[e] = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
initalizeUnit(id: number, stats: Record<string, number>): void{
|
||||||
|
this.units.set(id, this.validateStats(stats));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract update(deltaT: number): void;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { GameEventType } from "../Events/GameEventType";
|
||||||
export default class Input {
|
export default class Input {
|
||||||
private static mousePressed: boolean;
|
private static mousePressed: boolean;
|
||||||
private static mouseJustPressed: boolean;
|
private static mouseJustPressed: boolean;
|
||||||
|
private static mouseButtonPressed: number;
|
||||||
|
|
||||||
private static keyJustPressed: Map<boolean>;
|
private static keyJustPressed: Map<boolean>;
|
||||||
private static keyPressed: Map<boolean>;
|
private static keyPressed: Map<boolean>;
|
||||||
|
@ -35,7 +36,7 @@ export default class Input {
|
||||||
* Initializes the Input object
|
* Initializes the Input object
|
||||||
* @param viewport A reference to the viewport of the game
|
* @param viewport A reference to the viewport of the game
|
||||||
*/
|
*/
|
||||||
static initialize(viewport: Viewport, keyMap: Array<Record<string, any>>){
|
static initialize(viewport: Viewport, keyMap: Array<Record<string, any>>) {
|
||||||
Input.viewport = viewport;
|
Input.viewport = viewport;
|
||||||
Input.mousePressed = false;
|
Input.mousePressed = false;
|
||||||
Input.mouseJustPressed = false;
|
Input.mouseJustPressed = false;
|
||||||
|
@ -53,7 +54,7 @@ export default class Input {
|
||||||
Input.keyMap = new Map();
|
Input.keyMap = new Map();
|
||||||
|
|
||||||
// Add all keys to the keymap
|
// Add all keys to the keymap
|
||||||
for(let entry in keyMap){
|
for (let entry in keyMap) {
|
||||||
let name = keyMap[entry].name;
|
let name = keyMap[entry].name;
|
||||||
let keys = keyMap[entry].keys;
|
let keys = keyMap[entry].keys;
|
||||||
Input.keyMap.add(name, keys);
|
Input.keyMap.add(name, keys);
|
||||||
|
@ -62,7 +63,7 @@ export default class Input {
|
||||||
Input.eventQueue = EventQueue.getInstance();
|
Input.eventQueue = EventQueue.getInstance();
|
||||||
// Subscribe to all input events
|
// Subscribe to all input events
|
||||||
Input.eventQueue.subscribe(Input.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE,
|
Input.eventQueue.subscribe(Input.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE,
|
||||||
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]);
|
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static update(deltaT: number): void {
|
static update(deltaT: number): void {
|
||||||
|
@ -72,53 +73,54 @@ export default class Input {
|
||||||
Input.justScrolled = false;
|
Input.justScrolled = false;
|
||||||
Input.scrollDirection = 0;
|
Input.scrollDirection = 0;
|
||||||
|
|
||||||
while(Input.receiver.hasNextEvent()){
|
while (Input.receiver.hasNextEvent()) {
|
||||||
let event = Input.receiver.getNextEvent();
|
let event = Input.receiver.getNextEvent();
|
||||||
|
|
||||||
// Handle each event type
|
// Handle each event type
|
||||||
if(event.type === GameEventType.MOUSE_DOWN){
|
if (event.type === GameEventType.MOUSE_DOWN) {
|
||||||
Input.mouseJustPressed = true;
|
Input.mouseJustPressed = true;
|
||||||
Input.mousePressed = true;
|
Input.mousePressed = true;
|
||||||
Input.mousePressPosition = event.data.get("position");
|
Input.mousePressPosition = event.data.get("position");
|
||||||
|
Input.mouseButtonPressed = event.data.get("button");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.MOUSE_UP){
|
if (event.type === GameEventType.MOUSE_UP) {
|
||||||
Input.mousePressed = false;
|
Input.mousePressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.MOUSE_MOVE){
|
if (event.type === GameEventType.MOUSE_MOVE) {
|
||||||
Input.mousePosition = event.data.get("position");
|
Input.mousePosition = event.data.get("position");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.KEY_DOWN){
|
if (event.type === GameEventType.KEY_DOWN) {
|
||||||
let key = event.data.get("key");
|
let key = event.data.get("key");
|
||||||
// Handle space bar
|
// Handle space bar
|
||||||
if(key === " "){
|
if (key === " ") {
|
||||||
key = "space";
|
key = "space";
|
||||||
}
|
}
|
||||||
if(!Input.keyPressed.get(key)){
|
if (!Input.keyPressed.get(key)) {
|
||||||
Input.keyJustPressed.set(key, true);
|
Input.keyJustPressed.set(key, true);
|
||||||
Input.keyPressed.set(key, true);
|
Input.keyPressed.set(key, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.KEY_UP){
|
if (event.type === GameEventType.KEY_UP) {
|
||||||
let key = event.data.get("key");
|
let key = event.data.get("key");
|
||||||
// Handle space bar
|
// Handle space bar
|
||||||
if(key === " "){
|
if (key === " ") {
|
||||||
key = "space";
|
key = "space";
|
||||||
}
|
}
|
||||||
Input.keyPressed.set(key, false);
|
Input.keyPressed.set(key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.CANVAS_BLUR){
|
if (event.type === GameEventType.CANVAS_BLUR) {
|
||||||
Input.clearKeyPresses()
|
Input.clearKeyPresses()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event.type === GameEventType.WHEEL_UP){
|
if (event.type === GameEventType.WHEEL_UP) {
|
||||||
Input.scrollDirection = -1;
|
Input.scrollDirection = -1;
|
||||||
Input.justScrolled = true;
|
Input.justScrolled = true;
|
||||||
} else if(event.type === GameEventType.WHEEL_DOWN){
|
} else if (event.type === GameEventType.WHEEL_DOWN) {
|
||||||
Input.scrollDirection = 1;
|
Input.scrollDirection = 1;
|
||||||
Input.justScrolled = true;
|
Input.justScrolled = true;
|
||||||
}
|
}
|
||||||
|
@ -137,9 +139,9 @@ export default class Input {
|
||||||
* @returns True if the key was just pressed, false otherwise
|
* @returns True if the key was just pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isKeyJustPressed(key: string): boolean {
|
static isKeyJustPressed(key: string): boolean {
|
||||||
if(Input.keysDisabled) return false;
|
if (Input.keysDisabled) return false;
|
||||||
|
|
||||||
if(Input.keyJustPressed.has(key)){
|
if (Input.keyJustPressed.has(key)) {
|
||||||
return Input.keyJustPressed.get(key)
|
return Input.keyJustPressed.get(key)
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -152,11 +154,11 @@ export default class Input {
|
||||||
* @returns An array of all of the newly pressed keys.
|
* @returns An array of all of the newly pressed keys.
|
||||||
*/
|
*/
|
||||||
static getKeysJustPressed(): Array<string> {
|
static getKeysJustPressed(): Array<string> {
|
||||||
if(Input.keysDisabled) return [];
|
if (Input.keysDisabled) return [];
|
||||||
|
|
||||||
let keys = Array<string>();
|
let keys = Array<string>();
|
||||||
Input.keyJustPressed.forEach(key => {
|
Input.keyJustPressed.forEach(key => {
|
||||||
if(Input.keyJustPressed.get(key)){
|
if (Input.keyJustPressed.get(key)) {
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -169,9 +171,9 @@ export default class Input {
|
||||||
* @returns True if the key is currently pressed, false otherwise
|
* @returns True if the key is currently pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isKeyPressed(key: string): boolean {
|
static isKeyPressed(key: string): boolean {
|
||||||
if(Input.keysDisabled) return false;
|
if (Input.keysDisabled) return false;
|
||||||
|
|
||||||
if(Input.keyPressed.has(key)){
|
if (Input.keyPressed.has(key)) {
|
||||||
return Input.keyPressed.get(key)
|
return Input.keyPressed.get(key)
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -200,20 +202,20 @@ export default class Input {
|
||||||
* @returns True if the input was just pressed, false otherwise
|
* @returns True if the input was just pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isJustPressed(inputName: string): boolean {
|
static isJustPressed(inputName: string): boolean {
|
||||||
if(Input.keysDisabled) return false;
|
if (Input.keysDisabled) return false;
|
||||||
|
|
||||||
if(Input.keyMap.has(inputName)){
|
if (Input.keyMap.has(inputName)) {
|
||||||
const keys = Input.keyMap.get(inputName);
|
const keys = Input.keyMap.get(inputName);
|
||||||
let justPressed = false;
|
let justPressed = false;
|
||||||
|
|
||||||
for(let key of keys){
|
for (let key of keys) {
|
||||||
justPressed = justPressed || Input.isKeyJustPressed(key);
|
justPressed = justPressed || Input.isKeyJustPressed(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return justPressed;
|
return justPressed;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -222,13 +224,13 @@ export default class Input {
|
||||||
* @returns True if the input is pressed, false otherwise
|
* @returns True if the input is pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isPressed(inputName: string): boolean {
|
static isPressed(inputName: string): boolean {
|
||||||
if(Input.keysDisabled) return false;
|
if (Input.keysDisabled) return false;
|
||||||
|
|
||||||
if(Input.keyMap.has(inputName)){
|
if (Input.keyMap.has(inputName)) {
|
||||||
const keys = Input.keyMap.get(inputName);
|
const keys = Input.keyMap.get(inputName);
|
||||||
let pressed = false;
|
let pressed = false;
|
||||||
|
|
||||||
for(let key of keys){
|
for (let key of keys) {
|
||||||
pressed = pressed || Input.isKeyPressed(key);
|
pressed = pressed || Input.isKeyPressed(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,20 +239,30 @@ export default class Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the mouse was newly pressed Input frame
|
*
|
||||||
|
* Returns whether or not the mouse was newly pressed Input frame.
|
||||||
|
* @param mouseButton Optionally specify which mouse click you want to know was pressed.
|
||||||
|
* 0 for left click, 1 for middle click, 2 for right click.
|
||||||
* @returns True if the mouse was just pressed, false otherwise
|
* @returns True if the mouse was just pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isMouseJustPressed(): boolean {
|
static isMouseJustPressed(mouseButton?: number): boolean {
|
||||||
|
if (mouseButton) {
|
||||||
|
return Input.mouseJustPressed && !Input.mouseDisabled && mouseButton == this.mouseButtonPressed;
|
||||||
|
}
|
||||||
return Input.mouseJustPressed && !Input.mouseDisabled;
|
return Input.mouseJustPressed && !Input.mouseDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the mouse is currently pressed
|
* Returns whether or not the mouse is currently pressed
|
||||||
|
* @param mouseButton Optionally specify which mouse click you want to know was pressed.
|
||||||
|
* 0 for left click, 1 for middle click, 2 for right click.
|
||||||
* @returns True if the mouse is currently pressed, false otherwise
|
* @returns True if the mouse is currently pressed, false otherwise
|
||||||
*/
|
*/
|
||||||
static isMousePressed(): boolean {
|
static isMousePressed(mouseButton?: number): boolean {
|
||||||
|
if (mouseButton) {
|
||||||
|
return Input.mousePressed && !Input.mouseDisabled && mouseButton == this.mouseButtonPressed;
|
||||||
|
}
|
||||||
return Input.mousePressed && !Input.mouseDisabled;
|
return Input.mousePressed && !Input.mouseDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +287,7 @@ export default class Input {
|
||||||
* @returns The mouse position stored as a Vec2
|
* @returns The mouse position stored as a Vec2
|
||||||
*/
|
*/
|
||||||
static getMousePosition(): Vec2 {
|
static getMousePosition(): Vec2 {
|
||||||
return Input.mousePosition.scaled(1/this.viewport.getZoomLevel());
|
return Input.mousePosition.scaled(1 / this.viewport.getZoomLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -284,7 +296,7 @@ export default class Input {
|
||||||
* @returns The mouse position stored as a Vec2
|
* @returns The mouse position stored as a Vec2
|
||||||
*/
|
*/
|
||||||
static getGlobalMousePosition(): Vec2 {
|
static getGlobalMousePosition(): Vec2 {
|
||||||
return Input.mousePosition.clone().scale(1/this.viewport.getZoomLevel()).add(Input.viewport.getOrigin());
|
return Input.mousePosition.clone().scale(1 / this.viewport.getZoomLevel()).add(Input.viewport.getOrigin());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,8 @@ export default class InputHandler {
|
||||||
|
|
||||||
private handleMouseDown = (event: MouseEvent, canvas: HTMLCanvasElement): void => {
|
private handleMouseDown = (event: MouseEvent, canvas: HTMLCanvasElement): void => {
|
||||||
let pos = this.getMousePosition(event, canvas);
|
let pos = this.getMousePosition(event, canvas);
|
||||||
let gameEvent = new GameEvent(GameEventType.MOUSE_DOWN, {position: pos});
|
let button = event.button;
|
||||||
|
let gameEvent = new GameEvent(GameEventType.MOUSE_DOWN, {position: pos, button: button});
|
||||||
this.eventQueue.addEvent(gameEvent);
|
this.eventQueue.addEvent(gameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import TweenController from "../Rendering/Animations/TweenController";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
import Color from "../Utils/Color";
|
import Color from "../Utils/Color";
|
||||||
import Circle from "../DataTypes/Shapes/Circle";
|
import Circle from "../DataTypes/Shapes/Circle";
|
||||||
|
import GoapAI from "../DataTypes/Interfaces/GoapAI";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The representation of an object in the game world.
|
* The representation of an object in the game world.
|
||||||
|
@ -54,7 +55,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
isColliding: boolean = false;
|
isColliding: boolean = false;
|
||||||
|
|
||||||
/*---------- ACTOR ----------*/
|
/*---------- ACTOR ----------*/
|
||||||
_ai: AI;
|
_ai: AI | GoapAI;
|
||||||
aiActive: boolean;
|
aiActive: boolean;
|
||||||
path: NavigationPath;
|
path: NavigationPath;
|
||||||
pathfinding: boolean = false;
|
pathfinding: boolean = false;
|
||||||
|
@ -331,11 +332,11 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
}
|
}
|
||||||
|
|
||||||
/*---------- ACTOR ----------*/
|
/*---------- ACTOR ----------*/
|
||||||
get ai(): AI {
|
get ai(): AI | GoapAI {
|
||||||
return this._ai;
|
return this._ai;
|
||||||
}
|
}
|
||||||
|
|
||||||
set ai(ai: AI) {
|
set ai(ai: AI | GoapAI) {
|
||||||
if(!this._ai){
|
if(!this._ai){
|
||||||
// If we haven't been previously had an ai, register us with the ai manager
|
// If we haven't been previously had an ai, register us with the ai manager
|
||||||
this.scene.getAIManager().registerActor(this);
|
this.scene.getAIManager().registerActor(this);
|
||||||
|
@ -346,7 +347,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
}
|
}
|
||||||
|
|
||||||
// @implemented
|
// @implemented
|
||||||
addAI<T extends AI>(ai: string | (new () => T), options?: Record<string, any>): void {
|
addAI<T extends AI | GoapAI>(ai: string | (new () => T), options?: Record<string, any>, type?: number): void {
|
||||||
if(!this._ai){
|
if(!this._ai){
|
||||||
this.scene.getAIManager().registerActor(this);
|
this.scene.getAIManager().registerActor(this);
|
||||||
}
|
}
|
||||||
|
@ -357,6 +358,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
this._ai = new ai();
|
this._ai = new ai();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Question, how much do we want different type of AI to be handled the same, i.e. should GoapAI and AI similar methods and signatures for the sake of unity
|
||||||
this._ai.initializeAI(this, options);
|
this._ai.initializeAI(this, options);
|
||||||
|
|
||||||
this.aiActive = true;
|
this.aiActive = true;
|
||||||
|
|
|
@ -29,4 +29,28 @@ export default abstract class Graphic extends CanvasNode {
|
||||||
setColor(color: Color){
|
setColor(color: Color){
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set colorR(r: number){
|
||||||
|
this.color.r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorR(): number {
|
||||||
|
return this.color.r;
|
||||||
|
}
|
||||||
|
|
||||||
|
set colorG(g: number){
|
||||||
|
this.color.g = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorG(): number {
|
||||||
|
return this.color.g;
|
||||||
|
}
|
||||||
|
|
||||||
|
set colorB(b: number){
|
||||||
|
this.color.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorB(): number {
|
||||||
|
return this.color.b;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,4 +2,5 @@ export enum GraphicType {
|
||||||
POINT = "POINT",
|
POINT = "POINT",
|
||||||
RECT = "RECT",
|
RECT = "RECT",
|
||||||
LINE = "LINE",
|
LINE = "LINE",
|
||||||
|
PARTICLE = "PARTICLE"
|
||||||
}
|
}
|
57
src/Wolfie2D/Nodes/Graphics/Particle.ts
Normal file
57
src/Wolfie2D/Nodes/Graphics/Particle.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import Point from "./Point";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Position X
|
||||||
|
- Velocity (speed and direction) X
|
||||||
|
- Color X
|
||||||
|
- Lifetime
|
||||||
|
- Age can be handled as lifetime
|
||||||
|
- Shape X
|
||||||
|
- Size X
|
||||||
|
- Transparency X
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export default class Particle extends Point {
|
||||||
|
age: number;
|
||||||
|
|
||||||
|
inUse: boolean;
|
||||||
|
|
||||||
|
vel: Vec2;
|
||||||
|
|
||||||
|
mass: number;
|
||||||
|
|
||||||
|
constructor(position: Vec2, size: Vec2, mass: number) {
|
||||||
|
// Are we making this a circle?
|
||||||
|
super(position, size);
|
||||||
|
this.inUse = false;
|
||||||
|
this.mass = mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
setParticleActive(lifetime: number, position: Vec2) {
|
||||||
|
this.age = lifetime;
|
||||||
|
this.inUse = true;
|
||||||
|
this.visible = true;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
decrementAge(decay: number) {
|
||||||
|
this.age -= decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
setParticleInactive(){
|
||||||
|
this.inUse = false;
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set velY(y: number){
|
||||||
|
this.vel.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
get velY(): number {
|
||||||
|
return this.vel.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -4,9 +4,10 @@ import Vec2 from "../../DataTypes/Vec2";
|
||||||
/** A basic point to be drawn on the screen. */
|
/** A basic point to be drawn on the screen. */
|
||||||
export default class Point extends Graphic {
|
export default class Point extends Graphic {
|
||||||
|
|
||||||
constructor(position: Vec2){
|
constructor(position: Vec2, size: Vec2) {
|
||||||
|
// Are we making this a circle?
|
||||||
super();
|
super();
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.size.set(5, 5);
|
this.size.set(size.x, size.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -30,10 +30,11 @@ export default class NavigationManager {
|
||||||
* @param navName The name of the registered Navigable object
|
* @param navName The name of the registered Navigable object
|
||||||
* @param fromPosition The starting position of navigation
|
* @param fromPosition The starting position of navigation
|
||||||
* @param toPosition The ending position of Navigation
|
* @param toPosition The ending position of Navigation
|
||||||
|
* @param direct If true, go direct from fromPosition to toPosition, don't use NavMesh
|
||||||
* @returns A NavigationPath containing the route to take over the Navigable entity to get between the provided positions.
|
* @returns A NavigationPath containing the route to take over the Navigable entity to get between the provided positions.
|
||||||
*/
|
*/
|
||||||
getPath(navName: string, fromPosition: Vec2, toPosition: Vec2): NavigationPath {
|
getPath(navName: string, fromPosition: Vec2, toPosition: Vec2, direct?: boolean): NavigationPath {
|
||||||
let nav = this.navigableEntities.get(navName);
|
let nav = this.navigableEntities.get(navName);
|
||||||
return nav.getNavigationPath(fromPosition.clone(), toPosition.clone());
|
return nav.getNavigationPath(fromPosition.clone(), toPosition.clone(), direct);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,18 +21,23 @@ export default class Navmesh implements Navigable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @implemented
|
// @implemented
|
||||||
getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath {
|
getNavigationPath(fromPosition: Vec2, toPosition: Vec2, direct: boolean): NavigationPath {
|
||||||
let start = this.getClosestNode(fromPosition);
|
let start = this.getClosestNode(fromPosition);
|
||||||
let end = this.getClosestNode(toPosition);
|
let end = this.getClosestNode(toPosition);
|
||||||
|
|
||||||
let parent = GraphUtils.djikstra(this.graph, start);
|
|
||||||
|
|
||||||
let pathStack = new Stack<Vec2>(this.graph.numVertices);
|
let pathStack = new Stack<Vec2>(this.graph.numVertices);
|
||||||
|
|
||||||
// Push the final position and the final position in the graph
|
// Push the final position and the final position in the graph
|
||||||
pathStack.push(toPosition.clone());
|
pathStack.push(toPosition.clone());
|
||||||
|
|
||||||
|
if (direct) {
|
||||||
|
return new NavigationPath(pathStack);
|
||||||
|
}
|
||||||
|
|
||||||
pathStack.push(this.graph.positions[end]);
|
pathStack.push(this.graph.positions[end]);
|
||||||
|
|
||||||
|
let parent = GraphUtils.djikstra(this.graph, start);
|
||||||
|
|
||||||
// Add all parents along the path
|
// Add all parents along the path
|
||||||
let i = end;
|
let i = end;
|
||||||
while(parent[i] !== -1){
|
while(parent[i] !== -1){
|
||||||
|
|
163
src/Wolfie2D/Rendering/Animations/ParticleSystem.ts
Normal file
163
src/Wolfie2D/Rendering/Animations/ParticleSystem.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import Updateable from "../../DataTypes/Interfaces/Updateable";
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||||
|
import Particle from "../../Nodes/Graphics/Particle";
|
||||||
|
import Scene from "../../Scene/Scene";
|
||||||
|
import Timer from "../../Timing/Timer";
|
||||||
|
import Color from "../../Utils/Color";
|
||||||
|
import { EaseFunctionType } from "../../Utils/EaseFunctions";
|
||||||
|
import RandUtils from "../../Utils/RandUtils";
|
||||||
|
import ParticleSystemManager from "./ParticleSystemManager";
|
||||||
|
|
||||||
|
/*
|
||||||
|
-Move particle system to HW#4, particle class and particle manager(object pool), source, randomized period of decay,
|
||||||
|
semi-randomized approach for spawning, should be general purpose
|
||||||
|
and load some settings from a json (location, states, colors, randomization).
|
||||||
|
Should be effect when balloon is popped
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class ParticleSystem implements Updateable {
|
||||||
|
protected particlePool: Array<Particle>;
|
||||||
|
|
||||||
|
protected lifetime: number;
|
||||||
|
|
||||||
|
protected liveParticles: number;
|
||||||
|
|
||||||
|
protected maxLiveParticles: number;
|
||||||
|
|
||||||
|
protected sourcePoint: Vec2;
|
||||||
|
|
||||||
|
protected particleSize: Vec2;
|
||||||
|
|
||||||
|
protected systemLifetime: Timer;
|
||||||
|
|
||||||
|
protected systemRunning: boolean;
|
||||||
|
|
||||||
|
protected color: Color = new Color(255, 0, 0);
|
||||||
|
|
||||||
|
constructor(poolSize: number, sourcePoint: Vec2, lifetime: number, size: number, maxParticles: number) {
|
||||||
|
this.particlePool = new Array(poolSize);
|
||||||
|
this.sourcePoint = sourcePoint;
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
this.particleSize = new Vec2(size, size);
|
||||||
|
this.maxLiveParticles = maxParticles;
|
||||||
|
this.systemRunning = false;
|
||||||
|
|
||||||
|
ParticleSystemManager.getInstance().registerParticleSystem(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
initalizePool(scene: Scene, layer: string, type: ParticleSystemType, mass: number) {
|
||||||
|
for (let i = 0; i < this.particlePool.length; i++) {
|
||||||
|
this.particlePool[i] = <Particle>scene.add.graphic(GraphicType.PARTICLE, layer,
|
||||||
|
{ position: this.sourcePoint.clone(), size: this.particleSize.clone(), mass: mass });
|
||||||
|
this.particlePool[i].addPhysics();
|
||||||
|
this.particlePool[i].isCollidable = false;
|
||||||
|
this.particlePool[i].visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startSystem(time: number, startPoint?: Vec2) {
|
||||||
|
this.systemLifetime = new Timer(time);
|
||||||
|
this.systemLifetime.start();
|
||||||
|
this.systemRunning = true;
|
||||||
|
this.sourcePoint = startPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSystem() {
|
||||||
|
console.log(this);
|
||||||
|
this.systemRunning = false;
|
||||||
|
for (let particle of this.particlePool) {
|
||||||
|
if (particle.inUse) {
|
||||||
|
particle.setParticleInactive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeColor(color: Color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaT: number) {
|
||||||
|
if (!this.systemRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.systemLifetime.isStopped()) {
|
||||||
|
this.stopSystem();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (let particle of this.particlePool) {
|
||||||
|
if (particle.inUse) {
|
||||||
|
particle.decrementAge(deltaT * 1000);
|
||||||
|
|
||||||
|
if (particle.age <= 0) {
|
||||||
|
particle.setParticleInactive();
|
||||||
|
}
|
||||||
|
|
||||||
|
//particle.vel.y += 200*deltaT;
|
||||||
|
particle.move(particle.vel.scaled(deltaT));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
particle.setParticleActive(this.lifetime, this.sourcePoint.clone());
|
||||||
|
|
||||||
|
particle.color = this.color;
|
||||||
|
particle.alpha = 1;
|
||||||
|
//particle.size.set(1)
|
||||||
|
particle.vel = RandUtils.randVec(-50, 50, -100, 100);
|
||||||
|
|
||||||
|
particle.tweens.add("active", {
|
||||||
|
startDelay: 0,
|
||||||
|
duration: 2000,
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
property: "alpha",
|
||||||
|
resetOnComplete: true,
|
||||||
|
start: 1,
|
||||||
|
end: 0,
|
||||||
|
ease: EaseFunctionType.IN_OUT_SINE
|
||||||
|
},
|
||||||
|
/*{
|
||||||
|
property: "colorR",
|
||||||
|
resetOnComplete: true,
|
||||||
|
start: particle.color.r,
|
||||||
|
end: 255,
|
||||||
|
ease: EaseFunctionType.IN_OUT_SINE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: "colorG",
|
||||||
|
resetOnComplete: true,
|
||||||
|
start: particle.color.g,
|
||||||
|
end: 255,
|
||||||
|
ease: EaseFunctionType.IN_OUT_SINE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
property: "colorB",
|
||||||
|
resetOnComplete: true,
|
||||||
|
start: particle.color.b,
|
||||||
|
end: 255,
|
||||||
|
ease: EaseFunctionType.IN_OUT_SINE
|
||||||
|
},*/
|
||||||
|
{
|
||||||
|
property: "velY",
|
||||||
|
resetOnComplete: true,
|
||||||
|
start: particle.vel.y,
|
||||||
|
end: particle.vel.y + ((this.lifetime * particle.mass)/2),
|
||||||
|
ease: EaseFunctionType.IN_OUT_SINE
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
particle.tweens.play("active");
|
||||||
|
|
||||||
|
//particle.vel = RandUtils.randVec(-150, 150, -100, 100);
|
||||||
|
//console.log(particle.vel.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ParticleSystemType {
|
||||||
|
emitter = "emitter",
|
||||||
|
burst = "burst"
|
||||||
|
}
|
40
src/Wolfie2D/Rendering/Animations/ParticleSystemManager.ts
Normal file
40
src/Wolfie2D/Rendering/Animations/ParticleSystemManager.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import Updateable from "../../DataTypes/Interfaces/Updateable";
|
||||||
|
import ParticleSystem from "./ParticleSystem";
|
||||||
|
|
||||||
|
export default class ParticleSystemManager implements Updateable {
|
||||||
|
|
||||||
|
private static instance: ParticleSystemManager = null;
|
||||||
|
|
||||||
|
protected particleSystems: Array<ParticleSystem>;
|
||||||
|
|
||||||
|
private constructor(){
|
||||||
|
this.particleSystems = new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstance(): ParticleSystemManager {
|
||||||
|
if(ParticleSystemManager.instance === null){
|
||||||
|
ParticleSystemManager.instance = new ParticleSystemManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParticleSystemManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerParticleSystem(system: ParticleSystem){
|
||||||
|
this.particleSystems.push(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
deregisterParticleSystem(system: ParticleSystem){
|
||||||
|
let index = this.particleSystems.indexOf(system);
|
||||||
|
this.particleSystems.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearParticleSystems(){
|
||||||
|
this.particleSystems = new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(deltaT: number): void {
|
||||||
|
for(let particleSystem of this.particleSystems){
|
||||||
|
particleSystem.update(deltaT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import TextInput from "../../Nodes/UIElements/TextInput";
|
||||||
import Rect from "../../Nodes/Graphics/Rect";
|
import Rect from "../../Nodes/Graphics/Rect";
|
||||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||||
import Line from "../../Nodes/Graphics/Line";
|
import Line from "../../Nodes/Graphics/Line";
|
||||||
|
import Particle from "../../Nodes/Graphics/Particle";
|
||||||
|
|
||||||
// @ignorePage
|
// @ignorePage
|
||||||
|
|
||||||
|
@ -143,6 +144,9 @@ export default class CanvasNodeFactory {
|
||||||
case GraphicType.RECT:
|
case GraphicType.RECT:
|
||||||
instance = this.buildRect(options);
|
instance = this.buildRect(options);
|
||||||
break;
|
break;
|
||||||
|
case GraphicType.PARTICLE:
|
||||||
|
instance = this.buildParticle(options);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw `GraphicType '${type}' does not exist, or is registered incorrectly.`
|
throw `GraphicType '${type}' does not exist, or is registered incorrectly.`
|
||||||
}
|
}
|
||||||
|
@ -196,8 +200,18 @@ export default class CanvasNodeFactory {
|
||||||
|
|
||||||
buildPoint(options?: Record<string, any>): Point {
|
buildPoint(options?: Record<string, any>): Point {
|
||||||
this.checkIfPropExists("Point", options, "position", Vec2, "Vec2");
|
this.checkIfPropExists("Point", options, "position", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Point", options, "size", Vec2, "Vec2");
|
||||||
|
|
||||||
return new Point(options.position);
|
return new Point(options.position, options.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildParticle(options?: Record<string, any>): Point {
|
||||||
|
this.checkIfPropExists("Particle", options, "position", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Particle", options, "size", Vec2, "Vec2");
|
||||||
|
this.checkIfPropExists("Particle", options, "mass", "number", "number");
|
||||||
|
|
||||||
|
//Changed for testing
|
||||||
|
return new Particle(options.position, options.size, options.mass);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildLine(options?: Record<string, any>): Point {
|
buildLine(options?: Record<string, any>): Point {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import RenderingManager from "../Rendering/RenderingManager";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
import TimerManager from "../Timing/TimerManager";
|
import TimerManager from "../Timing/TimerManager";
|
||||||
import TweenManager from "../Rendering/Animations/TweenManager";
|
import TweenManager from "../Rendering/Animations/TweenManager";
|
||||||
|
import ParticleSystemManager from "../Rendering/Animations/ParticleSystemManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scenes are the main container in the game engine.
|
* Scenes are the main container in the game engine.
|
||||||
|
@ -173,6 +174,9 @@ export default class Scene implements Updateable {
|
||||||
// Update all tweens
|
// Update all tweens
|
||||||
TweenManager.getInstance().update(deltaT);
|
TweenManager.getInstance().update(deltaT);
|
||||||
|
|
||||||
|
// Update all particle systems
|
||||||
|
ParticleSystemManager.getInstance().update(deltaT);
|
||||||
|
|
||||||
// Update viewport
|
// Update viewport
|
||||||
this.viewport.update(deltaT);
|
this.viewport.update(deltaT);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
import Game from "./Wolfie2D/Loop/Game";
|
import Game from "./Wolfie2D/Loop/Game";
|
||||||
import default_scene from "./default_scene";
|
import default_scene from "./default_scene";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user