From 460d0e364333e3f1184e8d246a61aa2f5200d65b Mon Sep 17 00:00:00 2001 From: Joe Weaver Date: Thu, 18 Mar 2021 17:28:05 -0400 Subject: [PATCH] fixed some bugs and added destroy() methods --- .gitignore | 2 +- dist/hw4_assets/fonts/NoPixel.ttf | Bin 0 -> 10412 bytes dist/hw4_assets/sounds/jump-3.wav | Bin 0 -> 16652 bytes dist/hw4_assets/sprites/2bitbackground.png | Bin 0 -> 4787 bytes dist/hw4_assets/sprites/coin.png | Bin 0 -> 190 bytes dist/hw4_assets/spritesheets/ghostBunny.json | 22 + dist/hw4_assets/spritesheets/ghostBunny.png | Bin 0 -> 367 bytes dist/hw4_assets/spritesheets/hopper.json | 26 + dist/hw4_assets/spritesheets/hopper.png | Bin 0 -> 641 bytes .../spritesheets/platformPlayer.json | 27 + .../spritesheets/platformPlayer.png | Bin 0 -> 300 bytes dist/hw4_assets/tilemaps/level1.json | 558 +++++++++++ dist/hw4_assets/tilemaps/level2.json | 901 ++++++++++++++++++ dist/hw4_assets/tilemaps/platformer.png | Bin 0 -> 2002 bytes src/Homework4/Enemies/EnemyController.ts | 71 ++ src/Homework4/Enemies/EnemyState.ts | 31 + src/Homework4/Enemies/Idle.ts | 37 + src/Homework4/Enemies/Jump.ts | 34 + src/Homework4/Enemies/OnGround.ts | 26 + src/Homework4/Enemies/Walk.ts | 37 + src/Homework4/Player/PlayerController.ts | 89 ++ src/Homework4/Player/PlayerStates/Fall.ts | 18 + src/Homework4/Player/PlayerStates/Idle.ts | 36 + src/Homework4/Player/PlayerStates/InAir.ts | 19 + src/Homework4/Player/PlayerStates/Jump.ts | 108 +++ src/Homework4/Player/PlayerStates/OnGround.ts | 38 + .../Player/PlayerStates/PlayerState.ts | 30 + src/Homework4/Player/PlayerStates/Run.ts | 38 + src/Homework4/Player/PlayerStates/Walk.ts | 38 + src/Homework4/Scenes/GameLevel.ts | 362 +++++++ src/Homework4/Scenes/Level1.ts | 51 + src/Homework4/Scenes/Level2.ts | 49 + src/Homework4/Scenes/MainMenu.ts | 63 ++ src/Homework4/hw4_enums.ts | 11 + src/Wolfie2D/AI/AIManager.ts | 9 +- src/Wolfie2D/AI/ControllerAI.ts | 21 + src/Wolfie2D/AI/StateMachineAI.ts | 7 + src/Wolfie2D/DataTypes/Interfaces/AI.ts | 4 +- src/Wolfie2D/DataTypes/Interfaces/Actor.ts | 3 - src/Wolfie2D/DataTypes/Interfaces/Physical.ts | 37 +- src/Wolfie2D/DataTypes/List.ts | 63 ++ src/Wolfie2D/DataTypes/State/State.ts | 1 - src/Wolfie2D/Events/EventQueue.ts | 20 + src/Wolfie2D/Events/Receiver.ts | 5 + src/Wolfie2D/Input/Input.ts | 37 +- src/Wolfie2D/Loop/FixedUpdateGameLoop.ts | 19 +- src/Wolfie2D/Loop/Game.ts | 68 +- src/Wolfie2D/Loop/GameLoop.ts | 10 + src/Wolfie2D/Nodes/CanvasNode.ts | 9 + src/Wolfie2D/Nodes/GameNode.ts | 122 ++- src/Wolfie2D/Nodes/Graphic.ts | 8 + src/Wolfie2D/Nodes/Tilemap.ts | 4 + .../Nodes/Tilemaps/OrthogonalTilemap.ts | 21 +- src/Wolfie2D/Nodes/UIElements/Label.ts | 4 +- src/Wolfie2D/Physics/BasicPhysicsManager.ts | 129 ++- src/Wolfie2D/Physics/PhysicsManager.ts | 71 +- .../Registry/Registries/FontRegistry.ts | 0 .../Rendering/Animations/AnimationManager.ts | 6 +- .../Rendering/Animations/AnimationTypes.ts | 31 +- .../Rendering/Animations/TweenController.ts | 213 +++++ .../Rendering/Animations/TweenManager.ts | 177 +--- src/Wolfie2D/Rendering/CanvasRenderer.ts | 5 +- .../Scene/Factories/TilemapFactory.ts | 27 +- src/Wolfie2D/Scene/Layer.ts | 15 +- src/Wolfie2D/Scene/Scene.ts | 33 +- src/Wolfie2D/Scene/SceneManager.ts | 27 +- src/Wolfie2D/Scene/SceneOptions.ts | 19 +- src/Wolfie2D/SceneGraph/SceneGraph.ts | 30 +- src/Wolfie2D/SceneGraph/SceneGraphArray.ts | 4 +- src/Wolfie2D/SceneGraph/SceneGraphQuadTree.ts | 4 +- src/Wolfie2D/Timing/Timer.ts | 28 + src/demos/PlatformerPlayerController.ts | 5 +- src/index.html | 5 + src/main.ts | 14 +- 74 files changed, 3698 insertions(+), 339 deletions(-) create mode 100644 dist/hw4_assets/fonts/NoPixel.ttf create mode 100644 dist/hw4_assets/sounds/jump-3.wav create mode 100644 dist/hw4_assets/sprites/2bitbackground.png create mode 100644 dist/hw4_assets/sprites/coin.png create mode 100644 dist/hw4_assets/spritesheets/ghostBunny.json create mode 100644 dist/hw4_assets/spritesheets/ghostBunny.png create mode 100644 dist/hw4_assets/spritesheets/hopper.json create mode 100644 dist/hw4_assets/spritesheets/hopper.png create mode 100644 dist/hw4_assets/spritesheets/platformPlayer.json create mode 100644 dist/hw4_assets/spritesheets/platformPlayer.png create mode 100644 dist/hw4_assets/tilemaps/level1.json create mode 100644 dist/hw4_assets/tilemaps/level2.json create mode 100644 dist/hw4_assets/tilemaps/platformer.png create mode 100644 src/Homework4/Enemies/EnemyController.ts create mode 100644 src/Homework4/Enemies/EnemyState.ts create mode 100644 src/Homework4/Enemies/Idle.ts create mode 100644 src/Homework4/Enemies/Jump.ts create mode 100644 src/Homework4/Enemies/OnGround.ts create mode 100644 src/Homework4/Enemies/Walk.ts create mode 100644 src/Homework4/Player/PlayerController.ts create mode 100644 src/Homework4/Player/PlayerStates/Fall.ts create mode 100644 src/Homework4/Player/PlayerStates/Idle.ts create mode 100644 src/Homework4/Player/PlayerStates/InAir.ts create mode 100644 src/Homework4/Player/PlayerStates/Jump.ts create mode 100644 src/Homework4/Player/PlayerStates/OnGround.ts create mode 100644 src/Homework4/Player/PlayerStates/PlayerState.ts create mode 100644 src/Homework4/Player/PlayerStates/Run.ts create mode 100644 src/Homework4/Player/PlayerStates/Walk.ts create mode 100644 src/Homework4/Scenes/GameLevel.ts create mode 100644 src/Homework4/Scenes/Level1.ts create mode 100644 src/Homework4/Scenes/Level2.ts create mode 100644 src/Homework4/Scenes/MainMenu.ts create mode 100644 src/Homework4/hw4_enums.ts create mode 100644 src/Wolfie2D/AI/ControllerAI.ts create mode 100644 src/Wolfie2D/DataTypes/List.ts create mode 100644 src/Wolfie2D/Registry/Registries/FontRegistry.ts create mode 100644 src/Wolfie2D/Rendering/Animations/TweenController.ts diff --git a/.gitignore b/.gitignore index 07b29ed..8eddfef 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ dist/* !dist/builtin/ # Include the hw1 assets -!dist/hw1_assets/ +!dist/hw4_assets/ ### IF YOU ARE MAKING A PROJECT, YOU MAY WANT TO UNCOMMENT THIS LINE ### # !dist/assets/ diff --git a/dist/hw4_assets/fonts/NoPixel.ttf b/dist/hw4_assets/fonts/NoPixel.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c97b7b28ab6392e3021391497dc0401fbce12d34 GIT binary patch literal 10412 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 literal 0 HcmV?d00001 diff --git a/dist/hw4_assets/sounds/jump-3.wav b/dist/hw4_assets/sounds/jump-3.wav new file mode 100644 index 0000000000000000000000000000000000000000..80df671e51bada94933c7bcfd6144daa9d0fed49 GIT binary patch literal 16652 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 I|EK@>e{eza!2kdN literal 0 HcmV?d00001 diff --git a/dist/hw4_assets/sprites/2bitbackground.png b/dist/hw4_assets/sprites/2bitbackground.png new file mode 100644 index 0000000000000000000000000000000000000000..2f221a95b2b01bfa991f5a7b4f24f894a19a3ec1 GIT binary patch literal 4787 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 N002ovPDHLkV1f_RppO6m literal 0 HcmV?d00001 diff --git a/dist/hw4_assets/spritesheets/hopper.json b/dist/hw4_assets/spritesheets/hopper.json new file mode 100644 index 0000000..6306175 --- /dev/null +++ b/dist/hw4_assets/spritesheets/hopper.json @@ -0,0 +1,26 @@ +{ + "name": "Hopper", + "spriteSheetImage": "hopper.png", + "spriteWidth": 16, + "spriteHeight": 16, + "columns": 15, + "rows": 1, + "animations": [ + { + "name": "IDLE", + "frames": [ {"index": 0, "duration": 1} ] + }, + { + "name": "WALK", + "frames": [ {"index": 0, "duration": 16}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16}, {"index": 4, "duration": 16}, {"index": 5, "duration": 16}, {"index": 6, "duration": 16}, {"index": 7, "duration": 16} ] + }, + { + "name": "JUMP", + "frames":[ {"index": 8, "duration": 8}, {"index": 9, "duration": 8}, {"index": 10, "duration": 8}, {"index": 11, "duration": 8}] + }, + { + "name": "DYING", + "frames":[ {"index": 12, "duration": 8}, {"index": 13, "duration": 8}, {"index": 14, "duration": 8} ] + } + ] +} \ No newline at end of file diff --git a/dist/hw4_assets/spritesheets/hopper.png b/dist/hw4_assets/spritesheets/hopper.png new file mode 100644 index 0000000000000000000000000000000000000000..fb1a6cd3ee8c7d00b089d3b419765850374d8da6 GIT binary patch literal 641 zcmV-{0)G98P)Px%J4r-ARA_qX!F0s zvG`Fh|9SwVQmL@QwHuIHU!{~Y0KBqS&z0~a0DJ+!HzIy?@<#ypou2=u-x~kM;-xJ9 zeQH;${Z~Axl=4H*i1^Z5b}N-q3L;iLdZ0in01?qDm&vs_jc+xI$8)~LwerkiM|f}h zz2mpk{2Air^XNFhE>(Y(=jK$(u1rMnipf8x_a}|l%4?#zZ@*i4mYP4SO`frf#5Zbqc`l26>S=^$B}_gpuf@;F5uG2^wA-S5o-4L@c#S(~jqYuzrsQMuGM8Kgw#r-O z^1Wsx{$DlB!54?_lRA)%SGjT zcIpu$-bzgar&4>;Bbv0P3j>d?fYG)0{*U%6Iv-K$4m@&1v2ygX+Qr>-m-E(Nt7k9$ bPsPzcYkUTNyierP00000NkvXXu0mjftiL=c literal 0 HcmV?d00001 diff --git a/dist/hw4_assets/spritesheets/platformPlayer.json b/dist/hw4_assets/spritesheets/platformPlayer.json new file mode 100644 index 0000000..f67d69a --- /dev/null +++ b/dist/hw4_assets/spritesheets/platformPlayer.json @@ -0,0 +1,27 @@ +{ + "name": "PlatformPlayer", + "spriteSheetImage": "platformPlayer.png", + "spriteWidth": 16, + "spriteHeight": 16, + "columns": 7, + "rows": 1, + "durationType": "time", + "animations": [ + { + "name": "IDLE", + "frames": [ {"index": 0, "duration": 540}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16}, {"index": 0, "duration": 360}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16}] + }, + { + "name": "WALK", + "frames": [ {"index": 0, "duration": 16}, {"index": 4, "duration": 16}, {"index": 0, "duration": 16}, {"index": 5, "duration": 16} ] + }, + { + "name": "JUMP", + "frames":[ {"index": 6, "duration": 32}] + }, + { + "name": "FALL", + "frames":[ {"index": 4, "duration": 32}] + } + ] +} \ No newline at end of file diff --git a/dist/hw4_assets/spritesheets/platformPlayer.png b/dist/hw4_assets/spritesheets/platformPlayer.png new file mode 100644 index 0000000000000000000000000000000000000000..b62b00103081f471e853c9a6d60be23588ea2815 GIT binary patch literal 300 zcmV+{0n`48P)Px#=1D|BR9J=WnA;7*APhxultKDfq1}Ig&QNs}_lw8{gAIsBRNpVe^&v*Ykl>u@ z)B>&(5%DJofQ?JZ_;Gw>gm7sR#*gK93BF&ShbJY`gQwmMe)jw&e#;H0d4TbAc?eCv z;_uIk9VGP@X_M>Cx8{E~zNi9C3kwC^E{~&a{Cd0#gDF7Ungom=$46%%Rcb%x_HpCq z@Yw}SNt=N2{rQdStda5Kc)Hm36-FHhr@bvfjBiE&0O&KQuN}X1 y4pxD#{rr;r(u&nAvX5aa@L$zh9g<(mPx+j7da6RCt{2olSD&Dhz;est=N%DRBK1!j(tY_dzPkYsMcTTJ1i zu>=C~Be1@zRN9UCaU&xkguPj7tzoil+vYx7Yi-W4`;Oa&5xDCpZU44y+x_k1*lV{H z2+PYoR{nB-``A7G@^JUW5}?mN@0+!M@9L8OepwH9Pv+z64XtbS^buVAgO9H_;|oNp zvF=>IQ*)0)AyE3l)@M4g#K>-;s+Z(@ldSYai)U;O}lYOE`u0ssLCh57;JX83xv6kV9h zXd8fA|4!%|j=}Y9rx@+qKVy29A3)k#w!#Or0Uqw2_94apoC3-depY0$-QPZ1%o!95 z5W&tX`5cxv1EXz#YJ@t%|HHn6bPALw_PLc#>zSTnhIoKo1>_F%${L`405?pl;kP!Z zH;xh<%F+930|>cpUmrmFA4`MuNv;jhLZ@kZUV3ybfVv>0%*!qSR9&w&!CI&vppfNk zfKXRZ^fu-fh}i&Koh2zf#SDIc3nUc2bmt8D?p!RtstgVO!)we#fw&H_e2 z4H}V>j@baY`NQ*L!+4E|t*|~Z0)UMw(sU+9N&B&UE-$rC>ba26>NHRwEmR~{rV7Bx zIjDeA{o}wStyK3)(fJ`%KR_yfnBLl3nzR98dW)kIQ$I8%0!eauY5QyJ&dwqFe1KR( zavFzC0~L9IDgg5ZP&zR-w5bD#M()kRxw&>%aB9eoxou-H1YsGJ_<|W|wHDCZ2Dn-a z$Tjr*izsmgr|Ofe^C^_(&-ooo&iCuNgkO}Blee_N($Z7RVE0@&4{E5f9L&i-nwrbu z5Gi$3ni#F7Hpg9bRY1M zt;XPc1D#OR5&~DR`T>sLhdMpxZ-ERxXu5sy1Go>q0Qi+-bN1Q-05N)=Kgti_^40{4 zSGX9Heqb{s?Wzje(#waS@c<`BOh}l<1H{Yc61~pYd^9!}tb8eL+r-Y+`Nqg>Qw^GI4y{E>!bhjij4&2f#KL^di)MBj(vTZ7j*3Lw7#_6pyZ~fg8!igoK?1 zY&PNOZET``fN=l6`>Zy=H53~lmLDSL`m3)6xD_gB=g9~s(CB$~N~hPhDRRr_0@?sj zN2>>LO;97y?{Mw>brEj_j&}BN-LYIX(Ao|XEdXct15mD?;|GW?2P@}51>odFjk{h; zzagvo0kATBd`Hj_KY%n=^8@61`iA%cl=GkhFc1kUPHg}JE5-vjMB4zX`2nQu3s66R zqBg+UQ5%4O+5iJt%@0sJor|h~lcNG~a?}POpf?YG0Aci2d!k1G=*JilBbYN4^N*oBJ_k4C`(@S^5YYl)%=mdW*AI{)>|iJj ztK;SQmHiwiRRN9Qj6|_fr8X)64QSC}t%N|l3YckMKx+#gvI)388qosKdrDLSrH!?& z!-}`l#4-Ig9{UXOCJMo|zeDF^3UgOE995lA-ASmexPFPt*uxlDf-Vc7BZ!Mg>4P zt<{JMfKaF-2iHJX0btU@yBAvyHijnzDgaD4@d7D&2$u>#BYOJ%H6TO)J?H#2>rw$= z^78zeyxXZu#^HIbw}ly_YXNLp?E^?Tl3cnvf(mH4wo$Sw$hp&X8c3kmVwSt(bKJnt zxsX7#vN5Jp0jWi)8n3&}DFch(&F!$%$bqGciwUgxzTun|NNT;Y^RTp7o*Ww>wg&(z z&(9Cl(<~q)95Qn|H11WP>i_@% literal 0 HcmV?d00001 diff --git a/src/Homework4/Enemies/EnemyController.ts b/src/Homework4/Enemies/EnemyController.ts new file mode 100644 index 0000000..a51b0e5 --- /dev/null +++ b/src/Homework4/Enemies/EnemyController.ts @@ -0,0 +1,71 @@ +import Idle from "./Idle"; +import Jump from "./Jump"; +import Walk from "./Walk"; +import GameNode from "../../Wolfie2D/Nodes/GameNode"; +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import StateMachineAI from "../../Wolfie2D/AI/StateMachineAI"; +import { HW4_Events } from "../hw4_enums"; +import { EaseFunctionType } from "../../Wolfie2D/Utils/EaseFunctions"; + +export enum EnemyStates { + IDLE = "idle", + WALK = "walk", + JUMP = "jump", + PREVIOUS = "previous" +} + +export default class EnemyController extends StateMachineAI { + owner: GameNode; + jumpy: boolean; + direction: Vec2 = Vec2.ZERO; + velocity: Vec2 = Vec2.ZERO; + speed: number = 200; + + initializeAI(owner: GameNode, options: Record){ + this.owner = owner; + this.jumpy = options.jumpy ? options.jumpy : false; + + this.receiver.subscribe(HW4_Events.PLAYER_MOVE); + if(this.jumpy){ + this.receiver.subscribe(HW4_Events.PLAYER_JUMP); + this.speed = 100; + + // Give the owner a tween for the jump + owner.tweens.add("jump", { + startDelay: 0, + duration: 300, + effects: [ + { + property: "rotation", + resetOnComplete: true, + start: -3.14/8, + end: 3.14/8, + ease: EaseFunctionType.IN_OUT_SINE + } + ], + reverseOnComplete: true, + }); + } + + let idle = new Idle(this, owner); + this.addState(EnemyStates.IDLE, idle); + let walk = new Walk(this, owner); + this.addState(EnemyStates.WALK, walk); + let jump = new Jump(this, owner); + this.addState(EnemyStates.JUMP, jump); + + this.initialize(EnemyStates.IDLE); + } + + changeState(stateName: string): void { + + if(stateName === EnemyStates.JUMP){ + this.stack.push(this.stateMap.get(stateName)); + } + super.changeState(stateName); + } + + update(deltaT: number): void { + super.update(deltaT); + } +} \ No newline at end of file diff --git a/src/Homework4/Enemies/EnemyState.ts b/src/Homework4/Enemies/EnemyState.ts new file mode 100644 index 0000000..0161214 --- /dev/null +++ b/src/Homework4/Enemies/EnemyState.ts @@ -0,0 +1,31 @@ +import State from "../../Wolfie2D/DataTypes/State/State"; +import StateMachine from "../../Wolfie2D/DataTypes/State/StateMachine"; +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import GameNode from "../../Wolfie2D/Nodes/GameNode"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import EnemyController from "./EnemyController"; + +export default abstract class EnemyState extends State { + owner: GameNode; + gravity: number = 1000; + parent: EnemyController + + constructor(parent: StateMachine, owner: GameNode){ + super(parent); + + this.owner = owner; + } + + handleInput(event: GameEvent): void {} + + update(deltaT: number): void { + // Do gravity + this.parent.velocity.y += this.gravity*deltaT; + + if(this.owner.onWall){ + // Flip around + this.parent.direction.x *= -1; + (this.owner).invertX = !(this.owner).invertX; + } + } +} \ No newline at end of file diff --git a/src/Homework4/Enemies/Idle.ts b/src/Homework4/Enemies/Idle.ts new file mode 100644 index 0000000..bccff3b --- /dev/null +++ b/src/Homework4/Enemies/Idle.ts @@ -0,0 +1,37 @@ +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { HW4_Events } from "../hw4_enums"; +import { EnemyStates } from "./EnemyController"; +import OnGround from "./OnGround"; + +/** + * The idle enemy state. Enemies don't do anything until the player comes near them. + */ +export default class Idle extends OnGround { + onEnter(): void { + this.parent.speed = this.parent.speed; + (this.owner).animation.play("IDLE", true); + } + + onExit(): Record { + (this.owner).animation.stop(); + return {}; + } + + handleInput(event: GameEvent) { + if(event.type === HW4_Events.PLAYER_MOVE){ + let pos = event.data.get("position"); + if(this.owner.position.x - pos.x < (64*10)){ + this.finished(EnemyStates.WALK); + } + } + } + + update(deltaT: number): void { + super.update(deltaT); + + this.parent.velocity.x = 0; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + } +} \ No newline at end of file diff --git a/src/Homework4/Enemies/Jump.ts b/src/Homework4/Enemies/Jump.ts new file mode 100644 index 0000000..1e50b3a --- /dev/null +++ b/src/Homework4/Enemies/Jump.ts @@ -0,0 +1,34 @@ +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { EnemyStates } from "./EnemyController"; +import EnemyState from "./EnemyState"; + +export default class Jump extends EnemyState { + + onEnter(): void { + (this.owner).animation.play("JUMP", true); + (this.owner).tweens.play("jump", true); + this.gravity = 500; + } + + update(deltaT: number): void { + super.update(deltaT); + + if(this.owner.onGround){ + this.finished(EnemyStates.PREVIOUS); + } + + if(this.owner.onCeiling){ + this.parent.velocity.y = 0; + } + + this.parent.velocity.x += this.parent.direction.x * this.parent.speed/3.5 - 0.3*this.parent.velocity.x; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): Record { + (this.owner).animation.stop(); + (this.owner).tweens.stop("jump"); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Enemies/OnGround.ts b/src/Homework4/Enemies/OnGround.ts new file mode 100644 index 0000000..14ec299 --- /dev/null +++ b/src/Homework4/Enemies/OnGround.ts @@ -0,0 +1,26 @@ +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import { EnemyStates } from "./EnemyController"; +import EnemyState from "./EnemyState"; + +export default class OnGround extends EnemyState { + onEnter(): void {} + + handleInput(event: GameEvent): void { + super.handleInput(event); + } + + update(deltaT: number): void { + if(this.parent.velocity.y > 0){ + this.parent.velocity.y = 0; + } + super.update(deltaT); + + if(!this.owner.onGround && this.parent.jumpy && this.owner.active){ + this.finished(EnemyStates.JUMP); + } + } + + onExit(): Record { + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Enemies/Walk.ts b/src/Homework4/Enemies/Walk.ts new file mode 100644 index 0000000..4d12ecb --- /dev/null +++ b/src/Homework4/Enemies/Walk.ts @@ -0,0 +1,37 @@ +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { EnemyStates } from "./EnemyController"; +import OnGround from "./OnGround"; + +export default class Walk extends OnGround { + time: number; + + onEnter(): void { + if(this.parent.direction.isZero()){ + this.parent.direction = new Vec2(-1, 0); + (this.owner).invertX = true; + } + + (this.owner).animation.play("WALK", true); + + this.time = Date.now(); + } + + update(deltaT: number): void { + super.update(deltaT); + + if(this.parent.jumpy && (Date.now() - this.time > 500)){ + this.finished(EnemyStates.JUMP); + this.parent.velocity.y = -300; + } + + this.parent.velocity.x = this.parent.direction.x * this.parent.speed; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): Record { + (this.owner).animation.stop(); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerController.ts b/src/Homework4/Player/PlayerController.ts new file mode 100644 index 0000000..2deccca --- /dev/null +++ b/src/Homework4/Player/PlayerController.ts @@ -0,0 +1,89 @@ +import StateMachineAI from "../../Wolfie2D/AI/StateMachineAI"; +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Debug from "../../Wolfie2D/Debug/Debug"; +import GameNode from "../../Wolfie2D/Nodes/GameNode"; +import Sprite from "../../Wolfie2D/Nodes/Sprites/Sprite"; +import OrthogonalTilemap from "../../Wolfie2D/Nodes/Tilemaps/OrthogonalTilemap"; +import Fall from "./PlayerStates/Fall"; +import Idle from "./PlayerStates/Idle"; +import InAir from "./PlayerStates/InAir"; +import Jump from "./PlayerStates/Jump"; +import Run from "./PlayerStates/Run"; +import Walk from "./PlayerStates/Walk"; + +export enum PlayerType { + PLATFORMER = "platformer", + TOPDOWN = "topdown" +} + +export enum PlayerStates { + IDLE = "idle", + WALK = "walk", + RUN = "run", + JUMP = "jump", + FALL = "fall", + PREVIOUS = "previous" +} + +export default class PlayerController extends StateMachineAI { + protected owner: GameNode; + velocity: Vec2 = Vec2.ZERO; + speed: number = 200; + MIN_SPEED: number = 200; + MAX_SPEED: number = 300; + tilemap: OrthogonalTilemap; + coin: Sprite; + + initializeAI(owner: GameNode, options: Record){ + this.owner = owner; + + this.initializePlatformer(); + + this.tilemap = this.owner.getScene().getTilemap(options.tilemap) as OrthogonalTilemap; + this.coin = this.owner.getScene().add.sprite("coin", "coinLayer"); + this.coin.scale.set(2, 2); + } + + initializePlatformer(): void { + this.speed = 400; + + let idle = new Idle(this, this.owner); + this.addState(PlayerStates.IDLE, idle); + let walk = new Walk(this, this.owner); + this.addState(PlayerStates.WALK, walk); + let run = new Run(this, this.owner); + this.addState(PlayerStates.RUN, run); + let jump = new Jump(this, this.owner); + this.addState(PlayerStates.JUMP, jump); + let fall = new Fall(this, this.owner); + this.addState(PlayerStates.FALL, fall); + + this.initialize(PlayerStates.IDLE); + } + + changeState(stateName: string): void { + // If we jump or fall, push the state so we can go back to our current state later + // unless we're going from jump to fall or something + if((stateName === PlayerStates.JUMP || stateName === PlayerStates.FALL) && !(this.stack.peek() instanceof InAir)){ + this.stack.push(this.stateMap.get(stateName)); + } + + super.changeState(stateName); + } + + update(deltaT: number): void { + super.update(deltaT); + + if(this.currentState instanceof Jump){ + Debug.log("playerstate", "Player State: Jump"); + } else if (this.currentState instanceof Walk){ + Debug.log("playerstate", "Player State: Walk"); + } else if (this.currentState instanceof Run){ + Debug.log("playerstate", "Player State: Run"); + } else if (this.currentState instanceof Idle){ + Debug.log("playerstate", "Player State: Idle"); + } else if(this.currentState instanceof Fall){ + Debug.log("playerstate", "Player State: Fall"); + } + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/Fall.ts b/src/Homework4/Player/PlayerStates/Fall.ts new file mode 100644 index 0000000..8d8c5d9 --- /dev/null +++ b/src/Homework4/Player/PlayerStates/Fall.ts @@ -0,0 +1,18 @@ +import GameEvent from "../../../Wolfie2D/Events/GameEvent"; +import AnimatedSprite from "../../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import InAir from "./InAir"; + +export default class Fall extends InAir { + owner: AnimatedSprite; + + onEnter(options: Record): void { + this.owner.animation.play("FALL", true); + } + + handleInput(event: GameEvent): void {} + + onExit(): Record { + this.owner.animation.stop(); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/Idle.ts b/src/Homework4/Player/PlayerStates/Idle.ts new file mode 100644 index 0000000..22f6a21 --- /dev/null +++ b/src/Homework4/Player/PlayerStates/Idle.ts @@ -0,0 +1,36 @@ +import Input from "../../../Wolfie2D/Input/Input"; +import AnimatedSprite from "../../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { PlayerStates } from "../PlayerController"; +import OnGround from "./OnGround"; + +export default class Idle extends OnGround { + owner: AnimatedSprite; + + onEnter(options: Record): void { + this.parent.speed = this.parent.MIN_SPEED; + this.owner.animation.play("IDLE", true); + } + + update(deltaT: number): void { + super.update(deltaT); + + let dir = this.getInputDirection(); + + if(!dir.isZero() && dir.y === 0){ + if(Input.isPressed("run")){ + this.finished(PlayerStates.RUN); + } else { + this.finished(PlayerStates.WALK); + } + } + + this.parent.velocity.x = 0; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): Record { + this.owner.animation.stop(); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/InAir.ts b/src/Homework4/Player/PlayerStates/InAir.ts new file mode 100644 index 0000000..d13d71f --- /dev/null +++ b/src/Homework4/Player/PlayerStates/InAir.ts @@ -0,0 +1,19 @@ +import GameEvent from "../../../Wolfie2D/Events/GameEvent"; +import { PlayerStates } from "../PlayerController"; +import PlayerState from "./PlayerState"; + +export default abstract class InAir extends PlayerState { + update(deltaT: number): void { + super.update(deltaT); + + let dir = this.getInputDirection(); + + this.parent.velocity.x += dir.x * this.parent.speed/3.5 - 0.3*this.parent.velocity.x; + + this.owner.move(this.parent.velocity.scaled(deltaT)); + + if(this.owner.onGround){ + this.finished(PlayerStates.PREVIOUS); + } + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/Jump.ts b/src/Homework4/Player/PlayerStates/Jump.ts new file mode 100644 index 0000000..3e56b9f --- /dev/null +++ b/src/Homework4/Player/PlayerStates/Jump.ts @@ -0,0 +1,108 @@ +import GameEvent from "../../../Wolfie2D/Events/GameEvent"; +import AnimatedSprite from "../../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { EaseFunctionType } from "../../../Wolfie2D/Utils/EaseFunctions"; +import { HW4_Events } from "../../hw4_enums"; +import { PlayerStates } from "../PlayerController"; +import InAir from "./InAir"; + +export default class Jump extends InAir { + owner: AnimatedSprite; + + onEnter(options: Record): void { + this.owner.animation.play("JUMP", true); + } + + handleInput(event: GameEvent): void {} + + update(deltaT: number): void { + super.update(deltaT); + + if(this.owner.onCeiling){ + this.parent.velocity.y = 0; + } + + // If we're falling, go to the fall state + if(this.parent.velocity.y >= 0){ + this.finished(PlayerStates.FALL); + } + + if(this.owner.collidedWithTilemap && this.owner.onCeiling){ + // We collided with a tilemap above us. First, get the tile right above us + this.handleCoinblockCollision(); + } + } + + onExit(): Record { + this.owner.animation.stop(); + return {}; + } + + handleCoinblockCollision(){ + let pos = this.owner.position.clone(); + + // Go up plus some extra + pos.y -= (this.owner.collisionShape.halfSize.y + 10); + pos.x -= 16; + let rowCol = this.parent.tilemap.getColRowAt(pos); + let tile1 = this.parent.tilemap.getTileAtRowCol(rowCol); + pos.x += 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + let tile2 = this.parent.tilemap.getTileAtRowCol(rowCol); + pos.x += 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + let tile3 = this.parent.tilemap.getTileAtRowCol(rowCol); + + let t1 = tile1 === 17; + let t2 = tile2 === 17; + let t3 = tile3 === 17; + let air1 = tile1 === 0; + let air2 = tile2 === 0; + let air3 = tile3 === 0; + let majority = (t1 && t2) || (t1 && t3) || (t2 && t3) || (t1 && t2 && t3); + let minorityButAir = (t1 && air2 && air3) || (air1 && t2 && air3) || (air1 && air2 && t3); + + // If coin block, change to empty coin block + if(majority || minorityButAir){ + if(minorityButAir){ + // Get the correct position + if(t1){ + pos.x -= 32; + } else if(t2){ + pos.x -= 16; + } + rowCol = this.parent.tilemap.getColRowAt(pos); + } else { + pos.x -= 16; + rowCol = this.parent.tilemap.getColRowAt(pos); + } + + this.parent.tilemap.setTileAtRowCol(rowCol, 18); + this.emitter.fireEvent(HW4_Events.PLAYER_HIT_COIN_BLOCK); + + let tileSize = this.parent.tilemap.getTileSize(); + this.parent.coin.position.copy(rowCol.scale(tileSize.x, tileSize.y).add(tileSize.scaled(0.5))); + + // Animate collision + this.parent.coin.tweens.add("coin", { + startDelay: 0, + duration: 300, + effects: [{ + property: "positionY", + resetOnComplete: false, + start: this.parent.coin.position.y, + end: this.parent.coin.position.y - 2*tileSize.y, + ease: EaseFunctionType.OUT_SINE + }, + { + property: "alpha", + resetOnComplete: false, + start: 1, + end: 0, + ease: EaseFunctionType.OUT_SINE + }] + }); + + this.parent.coin.tweens.play("coin"); + } + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/OnGround.ts b/src/Homework4/Player/PlayerStates/OnGround.ts new file mode 100644 index 0000000..b60533e --- /dev/null +++ b/src/Homework4/Player/PlayerStates/OnGround.ts @@ -0,0 +1,38 @@ +import GameEvent from "../../../Wolfie2D/Events/GameEvent"; +import Input from "../../../Wolfie2D/Input/Input"; +import Sprite from "../../../Wolfie2D/Nodes/Sprites/Sprite"; +import MathUtils from "../../../Wolfie2D/Utils/MathUtils"; +import PlayerState from "./PlayerState"; + +export default class OnGround extends PlayerState { + onEnter(options: Record): void {} + + handleInput(event: GameEvent): void {} + + update(deltaT: number): void { + if(this.parent.velocity.y > 0){ + this.parent.velocity.y = 0; + } + super.update(deltaT); + + let direction = this.getInputDirection(); + + if(direction.x !== 0){ + (this.owner).invertX = MathUtils.sign(direction.x) < 0; + } + + if(Input.isJustPressed("jump")){ + this.finished("jump"); + this.parent.velocity.y = -500; + if(this.parent.velocity.x !== 0){ + this.owner.tweens.play("flip"); + } + } else if(!this.owner.onGround){ + this.finished("jump"); + } + } + + onExit(): Record { + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/PlayerState.ts b/src/Homework4/Player/PlayerStates/PlayerState.ts new file mode 100644 index 0000000..1183ecd --- /dev/null +++ b/src/Homework4/Player/PlayerStates/PlayerState.ts @@ -0,0 +1,30 @@ +import State from "../../../Wolfie2D/DataTypes/State/State"; +import StateMachine from "../../../Wolfie2D/DataTypes/State/StateMachine"; +import Vec2 from "../../../Wolfie2D/DataTypes/Vec2"; +import Input from "../../../Wolfie2D/Input/Input"; +import GameNode from "../../../Wolfie2D/Nodes/GameNode"; +import PlayerController from "../PlayerController"; + + +export default abstract class PlayerState extends State { + owner: GameNode; + gravity: number = 1000; + parent: PlayerController; + + constructor(parent: StateMachine, owner: GameNode){ + super(parent); + this.owner = owner; + } + + getInputDirection(): Vec2 { + let direction = Vec2.ZERO; + direction.x = (Input.isPressed("left") ? -1 : 0) + (Input.isPressed("right") ? 1 : 0); + direction.y = (Input.isJustPressed("jump") ? -1 : 0); + return direction; + } + + update(deltaT: number): void { + // Do gravity + this.parent.velocity.y += this.gravity*deltaT; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/Run.ts b/src/Homework4/Player/PlayerStates/Run.ts new file mode 100644 index 0000000..5fef920 --- /dev/null +++ b/src/Homework4/Player/PlayerStates/Run.ts @@ -0,0 +1,38 @@ +import Input from "../../../Wolfie2D/Input/Input"; +import AnimatedSprite from "../../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { HW4_Events } from "../../hw4_enums"; +import { PlayerStates } from "../PlayerController"; +import OnGround from "./OnGround"; + +export default class Run extends OnGround { + owner: AnimatedSprite; + + onEnter(options: Record): void { + this.parent.speed = this.parent.MAX_SPEED; + this.owner.animation.play("WALK", true); + } + + update(deltaT: number): void { + super.update(deltaT); + + let dir = this.getInputDirection(); + + if(dir.isZero()){ + this.finished(PlayerStates.IDLE); + } else { + if(!Input.isPressed("run")){ + this.finished(PlayerStates.WALK); + } + } + + this.parent.velocity.x = dir.x * this.parent.speed + + this.emitter.fireEvent(HW4_Events.PLAYER_MOVE, {position: this.owner.position.clone()}); + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): Record { + this.owner.animation.stop(); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Player/PlayerStates/Walk.ts b/src/Homework4/Player/PlayerStates/Walk.ts new file mode 100644 index 0000000..9e4db0f --- /dev/null +++ b/src/Homework4/Player/PlayerStates/Walk.ts @@ -0,0 +1,38 @@ +import Input from "../../../Wolfie2D/Input/Input"; +import AnimatedSprite from "../../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import { HW4_Events } from "../../hw4_enums"; +import { PlayerStates } from "../PlayerController"; +import OnGround from "./OnGround"; + +export default class Walk extends OnGround { + owner: AnimatedSprite; + + onEnter(options: Record): void { + this.parent.speed = this.parent.MIN_SPEED; + this.owner.animation.play("WALK", true); + } + + update(deltaT: number): void { + super.update(deltaT); + + let dir = this.getInputDirection(); + + if(dir.isZero()){ + this.finished(PlayerStates.IDLE); + } else { + if(Input.isPressed("run")){ + this.finished(PlayerStates.RUN); + } + } + + this.parent.velocity.x = dir.x * this.parent.speed + + this.emitter.fireEvent(HW4_Events.PLAYER_MOVE, {position: this.owner.position.clone()}); + this.owner.move(this.parent.velocity.scaled(deltaT)); + } + + onExit(): Record { + this.owner.animation.stop(); + return {}; + } +} \ No newline at end of file diff --git a/src/Homework4/Scenes/GameLevel.ts b/src/Homework4/Scenes/GameLevel.ts new file mode 100644 index 0000000..cbc8a3b --- /dev/null +++ b/src/Homework4/Scenes/GameLevel.ts @@ -0,0 +1,362 @@ +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Input from "../../Wolfie2D/Input/Input"; +import { TweenableProperties } from "../../Wolfie2D/Nodes/GameNode"; +import { GraphicType } from "../../Wolfie2D/Nodes/Graphics/GraphicTypes"; +import Rect from "../../Wolfie2D/Nodes/Graphics/Rect"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import Label from "../../Wolfie2D/Nodes/UIElements/Label"; +import { UIElementType } from "../../Wolfie2D/Nodes/UIElements/UIElementTypes"; +import Scene from "../../Wolfie2D/Scene/Scene"; +import Timer from "../../Wolfie2D/Timing/Timer"; +import Color from "../../Wolfie2D/Utils/Color"; +import { EaseFunctionType } from "../../Wolfie2D/Utils/EaseFunctions"; +import EnemyController from "../Enemies/EnemyController"; +import { HW4_Events } from "../hw4_enums"; +import PlayerController from "../Player/PlayerController"; + +export default class GameLevel extends Scene { + // Every level will have a player, which will be an animated sprite + protected playerSpawn: Vec2; + protected player: AnimatedSprite; + + // Labels for the UI + protected static coinCount: number = 0; + protected coinCountLabel: Label; + protected static livesCount: number = 3; + protected livesCountLabel: Label; + + // Stuff to end the level and go to the next level + protected levelEndArea: Rect; + protected nextLevel: new (...args: any) => GameLevel; + protected levelEndTimer: Timer; + protected levelEndLabel: Label; + + // Screen fade in/out for level start and end + protected levelTransitionTimer: Timer; + protected levelTransitionScreen: Rect; + + startScene(): void { + // Do the game level standard initializations + this.initLayers(); + this.initViewport(); + this.initPlayer(); + this.subscribeToEvents(); + this.addUI(); + + // Initialize the timers + this.levelTransitionTimer = new Timer(500); + this.levelEndTimer = new Timer(3000, () => { + // After the level end timer ends, fade to black and then go to the next scene + this.levelTransitionScreen.tweens.play("fadeIn"); + }); + + // Start the black screen fade out + this.levelTransitionScreen.tweens.play("fadeOut"); + + // Initially disable player movement + Input.disableInput(); + } + + updateScene(deltaT: number){ + // Handle events and update the UI if needed + while(this.receiver.hasNextEvent()){ + let event = this.receiver.getNextEvent(); + + switch(event.type){ + case HW4_Events.PLAYER_HIT_COIN: + { + // Hit a coin + let coin; + if(event.data.get("node") === this.player.id){ + // Other is coin, disable + coin = this.sceneGraph.getNode(event.data.get("other")); + } else { + // Node is coin, disable + coin = this.sceneGraph.getNode(event.data.get("node")); + } + + // Remove from physics and scene + coin.active = false; + coin.visible = false; + + // Increment our number of coins + this.incPlayerCoins(1); + } + break; + + case HW4_Events.PLAYER_HIT_COIN_BLOCK: + { + // Hit a coin block, so increment our number of coins + this.incPlayerCoins(1); + } + break; + + case HW4_Events.PLAYER_HIT_ENEMY: + { + let node = this.sceneGraph.getNode(event.data.get("node")); + let other = this.sceneGraph.getNode(event.data.get("other")); + + if(node === this.player){ + // Node is player, other is enemy + this.handlePlayerEnemyCollision(node, other); + } else { + // Other is player, node is enemy + this.handlePlayerEnemyCollision(other,node); + + } + } + break; + + case HW4_Events.ENEMY_DIED: + { + // An enemy finished its dying animation, hide it + let node = this.sceneGraph.getNode(event.data.get("owner")); + node.visible = false; + } + break; + + case HW4_Events.PLAYER_ENTERED_LEVEL_END: + { + if(!this.levelEndTimer.hasRun() && this.levelEndTimer.isStopped()){ + // The player has reached the end of the level + this.levelEndTimer.start(); + this.levelEndLabel.tweens.play("slideIn"); + } + } + break; + + case HW4_Events.LEVEL_START: + { + // Re-enable controls + console.log("Enabling input"); + Input.enableInput(); + } + break; + + case HW4_Events.LEVEL_END: + { + // Go to the next level + if(this.nextLevel){ + console.log("Going to next level!"); + let sceneOptions = { + physics: { + groupNames: ["ground", "player", "enemy", "coin"], + collisions: + [ + [0, 1, 1, 0], + [1, 0, 0, 1], + [1, 0, 0, 0], + [0, 1, 0, 0] + ] + } + } + this.sceneManager.changeToScene(this.nextLevel, {}, sceneOptions); + } + } + break; + + } + } + + // If player falls into a pit, kill them off and reset their position + if(this.player.position.y > 100*64){ + this.incPlayerLife(-1); + this.respawnPlayer(); + } + } + + protected initLayers(): void { + // Add a layer behind the tilemap for coinblock animation + this.addLayer("coinLayer", -50); + + // Add a layer for UI + this.addUILayer("UI"); + + // Add a layer for players and enemies + this.addLayer("primary", 1); + } + + protected initViewport(): void { + this.viewport.enableZoom(); + this.viewport.setZoomLevel(2); + } + + protected subscribeToEvents(){ + this.receiver.subscribe([ + HW4_Events.PLAYER_HIT_COIN, + HW4_Events.PLAYER_HIT_COIN_BLOCK, + HW4_Events.PLAYER_HIT_ENEMY, + HW4_Events.ENEMY_DIED, + HW4_Events.PLAYER_ENTERED_LEVEL_END, + HW4_Events.LEVEL_START, + HW4_Events.LEVEL_END + ]); + } + + protected addUI(){ + // In-game labels + this.coinCountLabel =