Browse code

add bunny

devnewton authored on 22/10/2016 at 15:54:15
Showing 10 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+License: CC-BY 3.0
1
+
2
+Authors: Stephen Challener (Redshrike), commissioned by Tebruno99
3
+
4
+http://opengameart.org/content/bunny-rabbit-lpc-style-for-pixelfarm
0 5
\ No newline at end of file
1 6
new file mode 100644
2 7
Binary files /dev/null and b/app/assets/sprites/lpc/bunny/bunny.png differ
3 8
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1
+<TextureAtlas imagePath="bunny01.png">
2
+    <!-- back -->
3
+    <SubTexture name="bunny.stand.back0" x="0" y="32" width="32" height="32" />
4
+    <SubTexture name="bunny.walk.back0" x="0" y="32" width="32" height="32" />
5
+    <SubTexture name="bunny.walk.back1" x="32" y="32" width="32" height="32" />
6
+    <SubTexture name="bunny.walk.back2" x="64" y="32" width="32" height="32" />
7
+    <SubTexture name="bunny.walk.back3" x="96" y="32" width="32" height="32" />
8
+    <SubTexture name="bunny.walk.back4" x="128" y="32" width="32" height="32" />
9
+    <SubTexture name="bunny.walk.back5" x="160" y="32" width="32" height="32" />
10
+    <SubTexture name="bunny.walk.back6" x="192" y="32" width="32" height="32" />
11
+    <!-- left -->
12
+    <SubTexture name="bunny.stand.left0" x="0" y="96" width="32" height="32" />
13
+    <SubTexture name="bunny.walk.left0" x="0" y="96" width="32" height="32" />
14
+    <SubTexture name="bunny.walk.left1" x="32" y="96" width="32" height="32" />
15
+    <SubTexture name="bunny.walk.left2" x="64" y="96" width="32" height="32" />
16
+    <SubTexture name="bunny.walk.left3" x="96" y="96" width="32" height="32" />
17
+    <SubTexture name="bunny.walk.left4" x="128" y="96" width="32" height="32" />
18
+    <SubTexture name="bunny.walk.left5" x="160" y="96" width="32" height="32" />
19
+    <SubTexture name="bunny.walk.left6" x="192" y="96" width="32" height="32" />
20
+    <!-- front -->
21
+    <SubTexture name="bunny.stand.front0" x="0" y="0" width="32" height="32" />
22
+    <SubTexture name="bunny.walk.front0" x="0" y="0" width="32" height="32" />
23
+    <SubTexture name="bunny.walk.front1" x="32" y="0" width="32" height="32" />
24
+    <SubTexture name="bunny.walk.front2" x="64" y="0" width="32" height="32" />
25
+    <SubTexture name="bunny.walk.front3" x="96" y="0" width="32" height="32" />
26
+    <SubTexture name="bunny.walk.front4" x="128" y="0" width="32" height="32" />
27
+    <SubTexture name="bunny.walk.front5" x="160" y="0" width="32" height="32" />
28
+    <SubTexture name="bunny.walk.front6" x="192" y="0" width="32" height="32" />
29
+    <!-- right -->
30
+    <SubTexture name="bunny.stand.right0" x="0" y="64" width="32" height="32" />
31
+    <SubTexture name="bunny.walk.right0" x="0" y="64" width="32" height="32" />
32
+    <SubTexture name="bunny.walk.right1" x="32" y="64" width="32" height="32" />
33
+    <SubTexture name="bunny.walk.right2" x="64" y="64" width="32" height="32" />
34
+    <SubTexture name="bunny.walk.right3" x="96" y="64" width="32" height="32" />
35
+    <SubTexture name="bunny.walk.right4" x="128" y="64" width="32" height="32" />
36
+    <SubTexture name="bunny.walk.right5" x="160" y="64" width="32" height="32" />
37
+    <SubTexture name="bunny.walk.right6" x="192" y="64" width="32" height="32" />
38
+</TextureAtlas>
0 39
new file mode 100644
... ...
@@ -0,0 +1,205 @@
0
+/// <reference path="../../typings/phaser.d.ts"/>
1
+import {ShmuprpgGame} from "../ShmuprpgGame.ts";
2
+import {CircularGun} from "./CircularGun.ts";
3
+import {Pathfinder} from "../ia/services/Pathfinder.ts";
4
+import * as b3 from "../ia/decisions/b3.ts";
5
+
6
+export class Bunny extends Phaser.Sprite {
7
+
8
+    birdExplosion: Phaser.Sprite;
9
+    pathfinder: Pathfinder;
10
+    blackboard: BunnyBlackboard;
11
+    weapon: CircularGun;
12
+    private damageTween: Phaser.Tween;
13
+
14
+    constructor(game: Phaser.Game, pathfinder: Pathfinder) {
15
+        super(game, 0, 0, 'bunny');
16
+        this.pathfinder = pathfinder;
17
+        this.game.physics.enable(this, Phaser.Physics.ARCADE);
18
+        this.anchor.setTo(0.5, 0.5);
19
+        this.checkWorldBounds = true;
20
+        this.outOfBoundsKill = true;
21
+        this.exists = false;
22
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.walk.front', 7);
23
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.walk.back', 7);
24
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.walk.right', 7);
25
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.walk.left', 7);
26
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.stand.front', 1);
27
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.stand.back', 1);
28
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.stand.right', 1);
29
+        (<ShmuprpgGame>this.game).addSpriteAnimation(this, 'bunny.stand.left', 1);
30
+        this.birdExplosion = this.game.add.sprite(this.x, this.y, 'bird-explosion');
31
+        this.birdExplosion.anchor.setTo(0.5, 0.5);
32
+        this.birdExplosion.exists = false;
33
+        const explodeAnimation = this.birdExplosion.animations.add('explode');
34
+        explodeAnimation.killOnComplete = true;
35
+        this.weapon = new CircularGun(this.game);
36
+    }
37
+
38
+    static preload(game: Phaser.Game) {
39
+        game.load.atlasXML('bunny', 'sprites/lpc/bunny/bunny.png', 'sprites/lpc/bunny/bunny.xml');
40
+    }
41
+
42
+    appears(fromX: number, fromY: number) {
43
+        const beforeBunny = this.game.add.sprite(fromX, fromY, 'before-bird');
44
+        beforeBunny.anchor.setTo(0.5, 0.5);
45
+        const beforeBunnyAnimation = beforeBunny.animations.add('appears');
46
+        beforeBunnyAnimation.onComplete.add(() => {
47
+            beforeBunny.destroy();
48
+            this.reset(fromX, fromY, 10);
49
+            this.blackboard = new BunnyBlackboard();
50
+        });
51
+        beforeBunnyAnimation.play(4, false);
52
+    }
53
+
54
+    update() {
55
+        super.update();
56
+        if (this.exists) {
57
+            this.executeBehaviorTree();
58
+        }
59
+    }
60
+
61
+    private executeBehaviorTree() {
62
+        BunnyB3.get().tick(this, this.blackboard);
63
+    }
64
+
65
+    damage(amount: number): Phaser.Sprite {
66
+        super.damage(amount);
67
+        if (!this.damageTween) {
68
+            this.damageTween = this.game.add.tween(this).from({ tint: 0xFF0000 }).to({ tint: 0xFFFFFF }, 500, Phaser.Easing.Linear.None, true, 0, 4, false);
69
+            this.damageTween.onComplete.add((): void => this.damageTween = null);
70
+        }
71
+        this.blackboard.fearLevel = 60;
72
+        return this;
73
+    }
74
+
75
+    kill(): Phaser.Sprite {
76
+        super.kill();
77
+        if(this.damageTween) {
78
+            this.damageTween.stop(true);
79
+        }
80
+        this.birdExplosion.reset(this.x, this.y);
81
+        this.birdExplosion.play('explode', 8, false);
82
+        return this;
83
+    }
84
+}
85
+
86
+class BunnyBlackboard extends b3.Blackboard {
87
+    currentPathPointTarget: Phaser.Point;
88
+    path: Array<Phaser.Point>;
89
+    fearLevel = 60;
90
+}
91
+
92
+class BunnyB3 extends b3.Tree<Bunny, BunnyBlackboard> {
93
+
94
+    private static singleton: BunnyB3;
95
+    static get() {
96
+        return this.singleton || (this.singleton = new BunnyB3());
97
+    }
98
+
99
+    constructor() {
100
+        super();
101
+        this.root = this.selector(
102
+            new ActionFollowPath(),
103
+            this.sequence(new ConditionFeelSafe(), new ActionAttack()),
104
+            new ActionSearchAPath());
105
+    }
106
+}
107
+
108
+
109
+class ActionFollowPath extends b3.Action<Bunny, BunnyBlackboard> {
110
+    tick(t: b3.Tick<Bunny, BunnyBlackboard>): b3.NodeState {
111
+        let me = t.me;
112
+        let currentPathPointTarget = t.blackboard.currentPathPointTarget;
113
+        if (currentPathPointTarget) {
114
+            this.moveToXY(me, currentPathPointTarget.x, currentPathPointTarget.y, 300);
115
+            if (Phaser.Math.distance(me.body.center.x, me.body.center.y, currentPathPointTarget.x, currentPathPointTarget.y) < me.body.halfWidth) {
116
+                t.blackboard.currentPathPointTarget = null;
117
+            }
118
+        } else {
119
+            let path = t.blackboard.path || [];
120
+            t.blackboard.currentPathPointTarget = path.shift();
121
+            if (path.length == 0) {
122
+                me.body.velocity.x = 0;
123
+                me.body.velocity.y = 0;
124
+                return b3.NodeState.FAILURE;
125
+            }
126
+        }
127
+        this.animate(me);
128
+        return b3.NodeState.RUNNING;
129
+    }
130
+
131
+    animate(me: Bunny) {
132
+        if (Math.abs(me.body.velocity.x) > Math.abs(me.body.velocity.y)) {
133
+            if (me.body.velocity.x < 0) {
134
+                me.play("bunny.walk.left", 8, false);
135
+            } else if (me.body.velocity.x > 0) {
136
+                me.play("bunny.walk.right", 8, false);
137
+            } else if (me.body.velocity.y < 0) {
138
+                me.play("bunny.walk.back", 8, false);
139
+            } else if (me.body.velocity.y > 0) {
140
+                me.play("bunny.walk.front", 8, false);
141
+            } else {
142
+                me.play("bunny.stand.front", 0, false);
143
+            }
144
+        } else {
145
+            if (me.body.velocity.y < 0) {
146
+                me.play("bunny.walk.back", 8, false);
147
+            } else if (me.body.velocity.y > 0) {
148
+                me.play("bunny.walk.front", 8, false);
149
+            } else if (me.body.velocity.x < 0) {
150
+                me.play("bunny.walk.left", 8, false);
151
+            } else if (me.body.velocity.x > 0) {
152
+                me.play("bunny.walk.right", 8, false);
153
+            } else {
154
+                me.play("bunny.stand.front", 0, false);
155
+            }
156
+        }
157
+    }
158
+
159
+    moveToXY(me: Bunny, x: number, y: number, speed: number = 60) {
160
+        var angle = Math.atan2(y - me.body.center.y, x - me.body.center.x);
161
+        me.body.velocity.x = Math.cos(angle) * speed;
162
+        me.body.velocity.y = Math.sin(angle) * speed;
163
+    }
164
+}
165
+
166
+class ActionSearchAPath extends b3.Action<Bunny, BunnyBlackboard> {
167
+
168
+    thinking = new b3.BlackboardKey<boolean>();
169
+
170
+    tick(t: b3.Tick<Bunny, BunnyBlackboard>): b3.NodeState {
171
+        let thinking = t.blackboard.get(this.thinking) || false;
172
+        let me = t.me;
173
+        if (!thinking) {
174
+            t.blackboard.set(this.thinking, true);
175
+            let targetPos = me.pathfinder.randomWalkablePos();
176
+            me.pathfinder.findPath(me.body.center.x, me.body.center.y, targetPos.x, targetPos.y, (path: Phaser.Point[]) => {
177
+                t.blackboard.path = path || [];
178
+                t.blackboard.set(this.thinking, false);
179
+            });
180
+        }
181
+        let path = t.blackboard.path || [];
182
+        return path.length > 0 ? b3.NodeState.SUCCESS : b3.NodeState.RUNNING;
183
+    }
184
+}
185
+
186
+class ConditionFeelSafe extends b3.Action<Bunny, BunnyBlackboard>  {
187
+    tick(t: b3.Tick<Bunny, BunnyBlackboard>): b3.NodeState {
188
+        let fearLevel = t.blackboard.fearLevel > 0 ? t.blackboard.fearLevel-- : 0;
189
+        if (fearLevel == 0) {
190
+            return b3.NodeState.SUCCESS;
191
+        }
192
+        if (fearLevel == 60) {
193
+            return b3.NodeState.FAILURE;
194
+        }
195
+        return b3.NodeState.RUNNING;
196
+    }
197
+}
198
+
199
+class ActionAttack extends b3.Action<Bunny, BunnyBlackboard> {
200
+    tick(t: b3.Tick<Bunny, BunnyBlackboard>): b3.NodeState {
201
+        t.me.weapon.fire(t.me.centerX, t.me.centerY);
202
+        return b3.NodeState.RUNNING;
203
+    }
204
+}
0 205
\ No newline at end of file
1 206
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+/// <reference path="../../typings/phaser.d.ts"/>
1
+import {Bullet} from "./Bullet.ts";
2
+
3
+export class CircularGun extends Phaser.Group {
4
+
5
+    bulletSpeed = 300;
6
+    fireRate = 800;
7
+    nextFireTime = 0;
8
+    bulletsByShots: number;
9
+
10
+    constructor(game: Phaser.Game, maxBullets: number = 64, bulletsByShots = 8) {
11
+        super(game);
12
+        this.bulletsByShots = bulletsByShots;
13
+        for (let i = 0; i < maxBullets; ++i) {
14
+            this.add(this.createBullet());
15
+        }
16
+    }
17
+
18
+    createBullet(): Bullet {
19
+        return new Bullet(this.game);
20
+    }
21
+
22
+    fire(fromX: number, fromY: number) {
23
+        if (this.game.time.time >= this.nextFireTime) {
24
+            for (let b = 1; b <= this.bulletsByShots; ++b) {
25
+                const bullet = <Bullet>this.getFirstExists(false);
26
+                if (bullet) {
27
+                    bullet.fire(fromX, fromY, b * 2 * Math.PI / this.bulletsByShots, this.bulletSpeed, 0, 0);
28
+                }
29
+            }
30
+            this.nextFireTime = this.game.time.time + this.fireRate;
31
+        }
32
+    }
33
+}
0 34
\ No newline at end of file
... ...
@@ -32,7 +32,7 @@ export class Grobelin extends Phaser.Sprite {
32 32
         const beforeGrobelinAnimation = beforeGrobelin.animations.add('appears');
33 33
         beforeGrobelinAnimation.onComplete.add(() => {
34 34
             beforeGrobelin.destroy();
35
-            this.reset(fromX, fromY, 50);
35
+            this.reset(fromX, fromY, 30);
36 36
             this.body.setSize(16, 16, 24, 48);
37 37
             this.body.collideWorldBounds = true;
38 38
             this.blackboard = new GrobelinBlackboard();
... ...
@@ -43,7 +43,9 @@ export class Grobelin extends Phaser.Sprite {
43 43
 
44 44
     kill(): Phaser.Sprite {
45 45
         super.kill();
46
-        this.damageTween.stop(true);
46
+        if (this.damageTween) {
47
+            this.damageTween.stop(true);
48
+        }
47 49
         this.tint = 0xFFFFFF;
48 50
         this.grobelinDeath.reset(this.x, this.y);
49 51
         this.grobelinDeath.animations.play("lpc.hurt", 6, false).killOnComplete = true;
... ...
@@ -156,7 +158,7 @@ class ActionFollowPath extends b3.Action<Grobelin, GrobelinBlackboard> {
156 156
             }
157 157
         } else {
158 158
             let path = t.blackboard.path || [];
159
-           t.blackboard.currentPathPointTarget =  path.shift();
159
+            t.blackboard.currentPathPointTarget = path.shift();
160 160
             if (path.length == 0) {
161 161
                 me.body.velocity.x = 0;
162 162
                 me.body.velocity.y = 0;
... ...
@@ -203,7 +205,7 @@ class ActionFollowPath extends b3.Action<Grobelin, GrobelinBlackboard> {
203 203
 }
204 204
 
205 205
 class ActionSearchAPathToEnemy extends b3.Action<Grobelin, GrobelinBlackboard> {
206
-    
206
+
207 207
     thinking = new b3.BlackboardKey<boolean>();
208 208
 
209 209
     tick(t: b3.Tick<Grobelin, GrobelinBlackboard>): b3.NodeState {
... ...
@@ -1,6 +1,6 @@
1 1
 /// <reference path="../../typings/phaser.d.ts"/>
2 2
 import { ShmuprpgGame } from "../ShmuprpgGame.ts";
3
-import { MachineGun } from "./MachineGun.ts";
3
+import { CircularGun } from "./MachineGun.ts";
4 4
 import { Controls } from "../utils/Controls.ts";
5 5
 import {Bullet} from "./Bullet.ts";
6 6
 
... ...
@@ -83,7 +83,7 @@ export class Hero extends Phaser.Sprite {
83 83
     }
84 84
 }
85 85
 
86
-class HeroMachineGun extends MachineGun {
86
+class HeroMachineGun extends CircularGun {
87 87
     createBullet(): Bullet {
88 88
         return new Bullet(this.game, 'bullet.blue');
89 89
     }
... ...
@@ -1,7 +1,7 @@
1 1
 /// <reference path="../../typings/phaser.d.ts"/>
2 2
 import {Bullet} from "./Bullet.ts";
3 3
 
4
-export class MachineGun extends Phaser.Group {
4
+export class CircularGun extends Phaser.Group {
5 5
 
6 6
     bulletSpeed = 600;
7 7
     fireRate = 200;
... ...
@@ -2,7 +2,7 @@
2 2
 import {Level} from "../states/Level.ts";
3 3
 import {Pathfinder} from "../ia/services/Pathfinder.ts";
4 4
 import * as b3 from "../ia/decisions/b3.ts";
5
-import {MachineGun} from "./MachineGun.ts";
5
+import {CircularGun} from "./MachineGun.ts";
6 6
 
7 7
 export class Spider extends Phaser.Sprite {
8 8
 
... ...
@@ -13,7 +13,7 @@ export class Spider extends Phaser.Sprite {
13 13
     private attackDangerousOffset: Phaser.Point;
14 14
     private damageTween: Phaser.Tween;
15 15
     private blackboard: SpiderBlackboard;
16
-    machineGun: MachineGun;
16
+    machineGun: CircularGun;
17 17
 
18 18
     constructor(game: Phaser.Game, pathfinder: Pathfinder) {
19 19
         super(game, 0, 0, 'spider');
... ...
@@ -26,7 +26,7 @@ export class Spider extends Phaser.Sprite {
26 26
         this.spiderDeath = this.game.add.sprite(this.x, this.y, 'spider');
27 27
         this.spiderDeath.anchor.setTo(0.5, 0.5);
28 28
         this.spiderDeath.exists = false;
29
-        this.machineGun = new MachineGun(this.game, 1);
29
+        this.machineGun = new CircularGun(this.game, 1);
30 30
     }
31 31
 
32 32
     appears(fromX: number, fromY: number, target: Phaser.Sprite) {
... ...
@@ -5,6 +5,7 @@ import {BirdFlock} from "../entities/BirdFlock.ts";
5 5
 import {GrobelinHorde} from "../entities/GrobelinHorde.ts";
6 6
 import {SpiderHorde} from "../entities/SpiderHorde.ts";
7 7
 import {Spider} from "../entities/Spider.ts";
8
+import {Bunny} from "../entities/Bunny.ts";
8 9
 
9 10
 import {Pathfinder} from "../ia/services/Pathfinder.ts";
10 11
 import {DamageResolver} from "../utils/DamageResolver.ts";
... ...
@@ -18,6 +19,7 @@ export class Level extends AbstractState {
18 18
     spiderHorde: SpiderHorde;
19 19
     pathfinder: Pathfinder;
20 20
     damageResolver: DamageResolver;
21
+    bunnyGroup: Phaser.Group;
21 22
 
22 23
     constructor() {
23 24
         super();
... ...
@@ -28,6 +30,7 @@ export class Level extends AbstractState {
28 28
         GrobelinHorde.preload(this.game);
29 29
         SpiderHorde.preload(this.game);
30 30
         Hero.preload(this.game);
31
+        Bunny.preload(this.game);
31 32
         this.game.load.tilemap('map', 'levels/level1.json', null, Phaser.Tilemap.TILED_JSON);
32 33
         this.game.load.image('terrains', 'sprites/lpc/terrains/terrains.png');
33 34
         this.game.load.image('cottage', 'sprites/lpc/thatched-roof-cottage/cottage.png');
... ...
@@ -81,12 +84,24 @@ export class Level extends AbstractState {
81 81
         this.spiderHorde = new SpiderHorde(this.hero, this.pathfinder, 0);
82 82
         this.game.add.existing(this.spiderHorde);
83 83
 
84
-        //this.game.time.events.add(1000, () => this.birdFlock.reset(this.hero, 10));
85
-        
84
+        this.bunnyGroup = this.game.add.group();
85
+
86 86
         this.game.time.events.add(1000, () => this.grobelinHorde.reset(this.hero, 3));
87
-        this.game.time.events.add(60000, () => this.spiderHorde.reset(this.hero, 4));
88
-        this.game.time.events.add(12000, () => this.birdFlock.reset(this.hero, 10));
89
-        
87
+        this.game.time.events.add(20 * 1000, () => this.spiderHorde.reset(this.hero, 4));
88
+        this.game.time.events.add(40 * 1000, () => this.birdFlock.reset(this.hero, 10));
89
+        this.game.time.events.add(60 * 1000, () => {
90
+            this.birdFlock.reset(this.hero, 0);
91
+            this.spiderHorde.reset(this.hero, 0);
92
+            this.grobelinHorde.reset(this.hero, 0)
93
+        });
94
+        this.game.time.events.add(70 * 1000, () => {
95
+            for (let i = 0; i < 4; ++i) {
96
+                let pos = this.pathfinder.randomWalkablePos();
97
+                let bunny = new Bunny(this.game, this.pathfinder);
98
+                bunny.appears(pos.x, pos.y);
99
+                this.bunnyGroup.add(bunny);
100
+            }
101
+        });
90 102
     }
91 103
 
92 104
     update() {
... ...
@@ -96,6 +111,11 @@ export class Level extends AbstractState {
96 96
     }
97 97
 
98 98
     resolveWeaponsEffects() {
99
+        this.damageResolver.groupVersusGroup(this.hero.weapon, this.bunnyGroup);
100
+        for(let bunny of this.bunnyGroup.children) {
101
+            this.damageResolver.spriteVersusGroup(this.hero, (<Bunny>bunny).weapon);
102
+        }
103
+        this.damageResolver.spriteVersusGroup(this.hero, this.birdFlock);
99 104
         this.damageResolver.groupVersusGroup(this.hero.weapon, this.birdFlock);
100 105
         this.damageResolver.spriteVersusGroup(this.hero, this.birdFlock);
101 106
         this.damageResolver.groupVersusGroup(this.hero.weapon, this.grobelinHorde);