Browse code

gamepad custom axis binding support

devnewton authored on 08/11/2016 17:21:28
Showing 7 changed files
... ...
@@ -7,6 +7,8 @@ import {Options} from "./states/Options";
7 7
 import {KeyboardOptions} from "./states/KeyboardOptions";
8 8
 import {KeyboardOptionsBindKey} from "./states/KeyboardOptionsBindKey";
9 9
 import {GamepadOptions} from "./states/GamepadOptions";
10
+import {GamepadOptionsLayout} from "./states/GamepadOptionsLayout";
11
+import {GamepadOptionsBindAxis} from "./states/GamepadOptionsBindAxis";
10 12
 import {Level} from "./states/Level";
11 13
 import {GameOver} from "./states/GameOver";
12 14
 import {Controls} from "./utils/Controls";
... ...
@@ -24,6 +26,8 @@ export class ShmuprpgGame extends Phaser.Game {
24 26
         this.state.add('Title', Title);
25 27
         this.state.add('Help', Help);
26 28
         this.state.add('Options', Options);
29
+        this.state.add('GamepadOptionsLayout', GamepadOptionsLayout);
30
+        this.state.add('GamepadOptionsBindAxis', GamepadOptionsBindAxis);
27 31
         this.state.add('KeyboardOptions', KeyboardOptions);
28 32
         this.state.add('KeyboardOptionsBindKey', KeyboardOptionsBindKey);
29 33
         this.state.add('GamepadOptions', GamepadOptions);
... ...
@@ -1,8 +1,6 @@
1 1
 /// <reference path="../../typings/phaser.d.ts"/>
2 2
 import {AbstractState} from "./AbstractState";
3 3
 import {MenuButton} from "../ui/MenuButton";
4
-import { ShmuprpgGame } from "../ShmuprpgGame";
5
-
6 4
 
7 5
 export class GamepadOptions extends AbstractState {
8 6
 
... ...
@@ -25,20 +23,16 @@ export class GamepadOptions extends AbstractState {
25 23
         subtitle.y = this.game.world.height - subtitle.height;
26 24
 
27 25
         new GamepadMenuButton(this.input.gamepad.pad1, 0xFF6666, "Gamepad 1", 500, 200, () => {
28
-            (<ShmuprpgGame>this.game).controls.usePad(this.input.gamepad.pad1);
29
-            this.game.state.start('Options');
26
+            this.game.state.start('GamepadOptionsLayout', true, false, 1);
30 27
         });
31 28
         new GamepadMenuButton(this.input.gamepad.pad2, 0x66FF66, "Gamepad 2", 500, 350, () => {
32
-            (<ShmuprpgGame>this.game).controls.usePad(this.input.gamepad.pad2);
33
-            this.game.state.start('Options');
29
+            this.game.state.start('GamepadOptionsLayout', true, false, 2);
34 30
         });
35 31
         new GamepadMenuButton(this.input.gamepad.pad3, 0x6666FF, "Gamepad 3", 500, 500, () => {
36
-            (<ShmuprpgGame>this.game).controls.usePad(this.input.gamepad.pad3);
37
-            this.game.state.start('Options');
32
+            this.game.state.start('GamepadOptionsLayout', true, false, 3);
38 33
         });
39 34
         new GamepadMenuButton(this.input.gamepad.pad4, 0xFFFF66, "Gamepad 4", 500, 650, () => {
40
-            (<ShmuprpgGame>this.game).controls.usePad(this.input.gamepad.pad4);
41
-            this.game.state.start('Options');
35
+            this.game.state.start('GamepadOptionsLayout', true, false, 4);
42 36
         });
43 37
         new MenuButton(this.game, "Back", 500, 800, () => this.game.state.start('Options'));
44 38
     }
45 39
new file mode 100644
... ...
@@ -0,0 +1,71 @@
1
+/// <reference path="../../typings/phaser.d.ts"/>
2
+import {AbstractState} from "./AbstractState";
3
+import {MenuButton} from "../ui/MenuButton";
4
+import {ShmuprpgGame} from "../ShmuprpgGame";
5
+
6
+
7
+export class GamepadOptionsBindAxis extends AbstractState {
8
+
9
+    bindings = [
10
+        { label: 'Pull move X axis', localStorageKeySuffix: 'moveXAxis' },
11
+        { label: 'Pull move Y axis', localStorageKeySuffix: 'moveYAxis' },
12
+        { label: 'Pull shoot X axis', localStorageKeySuffix: 'shootXAxis' },
13
+        { label: 'Pull shoot Y axis', localStorageKeySuffix: 'shootYAxis' }
14
+    ];
15
+
16
+    currentBinding: number = 0;
17
+    pad: Phaser.SinglePad;
18
+    padIndex = 1;
19
+    waitForNoInput: number;
20
+
21
+    constructor() {
22
+        super();
23
+    }
24
+
25
+    preload() {
26
+        MenuButton.preload(this.game);
27
+    }
28
+
29
+    init(padIndex: number, binding: number = 0) {
30
+        padIndex = padIndex || 1;
31
+        this.padIndex = 1;
32
+        this.pad = this.input.gamepad['pad' + this.padIndex];
33
+        if (binding >= this.bindings.length) {
34
+            this.currentBinding = 0;
35
+            (this.game as ShmuprpgGame).controls.useCustomGamepadLayout(padIndex);
36
+            this.game.state.start('GamepadOptions');
37
+        } else {
38
+            this.currentBinding = binding;
39
+        }
40
+        this.waitForNoInput = 60;
41
+    }
42
+
43
+    create() {
44
+        super.create();
45
+        let logo = this.game.add.text(this.game.world.centerX, 0, this.bindings[this.currentBinding].label, { font: "68px monospace", fill: 'white' });
46
+        logo.scale.x = 2;
47
+        logo.scale.y = 2;
48
+        logo.anchor.setTo(0.5, 0);
49
+        new MenuButton(this.game, "Back", 500, 900, () => this.game.state.start('GamepadOptions'));
50
+    }
51
+
52
+    update() {
53
+        super.update();
54
+        let activationZone = Math.max(0.99, Math.min(0.8, 4 * this.pad.deadZone));
55
+        for (var k in Phaser.Gamepad) {
56
+            if (k.startsWith('AXIS_')) {
57
+                let axisCode = Phaser.Gamepad[k];
58
+                if (Math.abs(this.pad.axis(axisCode)) >= activationZone) {
59
+                    if (this.waitForNoInput > 0) {
60
+                        return;
61
+                    } else {
62
+                        localStorage.setItem('gamepad' + this.padIndex + '.layout.custom.' + this.bindings[this.currentBinding].localStorageKeySuffix, axisCode);
63
+                        this.game.state.start('GamepadOptionsBindAxis', true, false, this.padIndex, this.currentBinding + 1);
64
+                        return;
65
+                    }
66
+                }
67
+            }
68
+        }
69
+        this.waitForNoInput--;
70
+    }
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,73 @@
1
+/// <reference path="../../typings/phaser.d.ts"/>
2
+import {AbstractState} from "./AbstractState";
3
+import {MenuButton} from "../ui/MenuButton";
4
+import {ShmuprpgGame} from "../ShmuprpgGame";
5
+
6
+export class GamepadOptionsLayout extends AbstractState {
7
+    padIndex: number;
8
+
9
+    constructor() {
10
+        super();
11
+    }
12
+
13
+    preload() {
14
+        MenuButton.preload(this.game);
15
+    }
16
+
17
+    init(padIndex : number) {
18
+        this.padIndex = padIndex || 1;
19
+    }
20
+
21
+    create() {
22
+        super.create();
23
+        let title = this.game.add.text(this.game.world.centerX, 0, 'Choose gamepad layout', { font: "68px monospace", fill: 'white' });
24
+        title.scale.x = 2;
25
+        title.scale.y = 2;
26
+        title.anchor.setTo(0.5, 0);
27
+
28
+        new MenuButton(this.game, "Xbox", 500, 200, () => {
29
+            (this.game as ShmuprpgGame).controls.useXboxLayout(this.padIndex);
30
+            this.game.state.start('Options');
31
+        });
32
+        new MenuButton(this.game, "Custom", 500, 350, () => {
33
+            this.game.state.start('GamepadOptionsBindAxis', true, false, this.padIndex);
34
+        });
35
+        new MenuButton(this.game, "Back", 500, 500, () => this.game.state.start('Options'));
36
+    }
37
+}
38
+
39
+class GamepadMenuButton extends MenuButton {
40
+    pad: Phaser.SinglePad;
41
+    activePadTint: number;
42
+    constructor(pad: Phaser.SinglePad, activePadTint: number, label: string, x: number, y: number, callback: Function) {
43
+        super(pad.game, label, x, y, callback);
44
+        this.pad = pad;
45
+        this.activePadTint = activePadTint;
46
+    }
47
+
48
+    update() {
49
+        super.update();
50
+
51
+        if (this.isPadActive()) {
52
+            this.tint = this.activePadTint;
53
+        } else {
54
+            this.tint = 0xFFFFFF;
55
+        }
56
+    }
57
+
58
+    isPadActive(): boolean {
59
+        for (let b = 0; b < 16; ++b) {
60
+            let button = this.pad.getButton(b);
61
+            if (button && button.isDown) {
62
+                return true;
63
+            }
64
+        }
65
+        for (let a = 0; a < 16; ++a) {
66
+            if (Math.abs(this.pad.axis(a)) > this.pad.deadZone) {
67
+                return true;
68
+            }
69
+        }
70
+        return false;
71
+    }
72
+
73
+}
... ...
@@ -30,11 +30,11 @@ export class KeyboardOptions extends AbstractState {
30 30
             this.game.state.start('Options');
31 31
         });
32 32
         new MenuButton(this.game, "Others ⬆⬇⬅➡ ikjl", 500, 600, () => {
33
-            (<ShmuprpgGame>this.game).controls.useOtherLayout();
33
+            (<ShmuprpgGame>this.game).controls.useOtherKeyboardLayout();
34 34
             this.game.state.start('Options');
35 35
         });
36 36
         new MenuButton(this.game, "Custom", 500, 750, () => {
37
-            (<ShmuprpgGame>this.game).controls.useOtherLayout();
37
+            (<ShmuprpgGame>this.game).controls.useOtherKeyboardLayout();
38 38
             this.game.state.start('KeyboardOptionsBindKey', true, false);
39 39
         });
40 40
         new MenuButton(this.game, "Back", 500, 900, () => this.game.state.start('Options'));
... ...
@@ -30,7 +30,7 @@ export class KeyboardOptionsBindKey extends AbstractState {
30 30
     init(binding: number = 0) {
31 31
         if (binding >= this.bindings.length) {
32 32
             this.currentBinding = 0;
33
-            (this.game as ShmuprpgGame).controls.useCustomLayout();
33
+            (this.game as ShmuprpgGame).controls.useCustomKeyboardLayout();
34 34
             this.game.state.start('KeyboardOptions');
35 35
         } else {
36 36
             this.currentBinding = binding;
... ...
@@ -12,13 +12,17 @@ export class Controls {
12 12
     keyCodeShootDown: number;
13 13
     keyCodeShootLeft: number;
14 14
     keyCodeShootRight: number;
15
+    moveXAxis: number;
16
+    moveYAxis: number;
17
+    shootXAxis: number;
18
+    shootYAxis: number;
15 19
 
16 20
     constructor(game: Phaser.Game) {
17 21
         this.game = game;
18 22
         game.input.gamepad.start();
19 23
         this.kb = game.input.keyboard;
20
-        this.pad = game.input.gamepad.pad1;
21 24
         this.setupKeyboardLayout();
25
+        this.setupGamepadLayout();
22 26
     }
23 27
 
24 28
     setupKeyboardLayout() {
... ...
@@ -28,16 +32,12 @@ export class Controls {
28 32
         } else if (layout == 'qwerty') {
29 33
             this.useQwertyLayout();
30 34
         } else if (layout == 'other') {
31
-            this.useOtherLayout();
35
+            this.useOtherKeyboardLayout();
32 36
         } else if (layout == 'custom') {
33
-            this.useCustomLayout();
37
+            this.useCustomKeyboardLayout();
34 38
         }
35 39
     }
36 40
 
37
-    usePad(pad: Phaser.SinglePad) {
38
-        this.pad = pad;
39
-    }
40
-
41 41
     useAzertyLayout() {
42 42
         this.keyCodeMoveUp = Phaser.KeyCode.Z;
43 43
         this.keyCodeMoveDown = Phaser.KeyCode.S;
... ...
@@ -62,7 +62,7 @@ export class Controls {
62 62
         localStorage.setItem('keyboard.layout', 'qwerty');
63 63
     }
64 64
 
65
-    useOtherLayout() {
65
+    useOtherKeyboardLayout() {
66 66
         this.keyCodeMoveUp = Phaser.KeyCode.UP;
67 67
         this.keyCodeMoveDown = Phaser.KeyCode.DOWN;
68 68
         this.keyCodeMoveLeft = Phaser.KeyCode.LEFT;
... ...
@@ -74,18 +74,59 @@ export class Controls {
74 74
         localStorage.setItem('keyboard.layout', 'other');
75 75
     }
76 76
 
77
-    useCustomLayout() {
78
-        this.keyCodeMoveUp = parseInt(localStorage.getItem('keyboard.layout.custom.moveUp')) || Phaser.KeyCode.UP;
79
-        this.keyCodeMoveDown = parseInt(localStorage.getItem('keyboard.layout.custom.moveDown')) || Phaser.KeyCode.DOWN;
80
-        this.keyCodeMoveLeft = parseInt(localStorage.getItem('keyboard.layout.custom.moveLeft')) || Phaser.KeyCode.LEFT;
81
-        this.keyCodeMoveRight = parseInt(localStorage.getItem('keyboard.layout.custom.moveRight')) || Phaser.KeyCode.RIGHT;
82
-        this.keyCodeShootUp = parseInt(localStorage.getItem('keyboard.layout.custom.shootUp')) || Phaser.KeyCode.I;
83
-        this.keyCodeShootDown = parseInt(localStorage.getItem('keyboard.layout.custom.shootDown')) || Phaser.KeyCode.K;
84
-        this.keyCodeShootLeft = parseInt(localStorage.getItem('keyboard.layout.custom.shootLeft')) || Phaser.KeyCode.J;
85
-        this.keyCodeShootRight = parseInt(localStorage.getItem('keyboard.layout.custom.shootRight')) || Phaser.KeyCode.L;
77
+    useCustomKeyboardLayout() {
78
+        this.keyCodeMoveUp = this.readNumberFromLocalStorage('keyboard.layout.custom.moveUp', Phaser.KeyCode.UP);
79
+        this.keyCodeMoveDown = this.readNumberFromLocalStorage('keyboard.layout.custom.moveDown', Phaser.KeyCode.DOWN);
80
+        this.keyCodeMoveLeft = this.readNumberFromLocalStorage('keyboard.layout.custom.moveLeft', Phaser.KeyCode.LEFT);
81
+        this.keyCodeMoveRight = this.readNumberFromLocalStorage('keyboard.layout.custom.moveRight', Phaser.KeyCode.RIGHT);
82
+        this.keyCodeShootUp = this.readNumberFromLocalStorage('keyboard.layout.custom.shootUp', Phaser.KeyCode.I);
83
+        this.keyCodeShootDown = this.readNumberFromLocalStorage('keyboard.layout.custom.shootDown', Phaser.KeyCode.K);
84
+        this.keyCodeShootLeft = this.readNumberFromLocalStorage('keyboard.layout.custom.shootLeft', Phaser.KeyCode.J);
85
+        this.keyCodeShootRight = this.readNumberFromLocalStorage('keyboard.layout.custom.shootRight', Phaser.KeyCode.L);
86 86
         localStorage.setItem('keyboard.layout', 'custom');
87 87
     }
88 88
 
89
+    setupGamepadLayout() {
90
+        let padIndex = parseInt(localStorage.getItem('gamepad')) || 1;
91
+        let layout = localStorage.getItem('gamepad' + padIndex + '.layout');
92
+        if (null == layout || layout == 'xbox') {
93
+            this.useXboxLayout(padIndex);
94
+        } else if (layout == 'custom') {
95
+            this.useCustomGamepadLayout(padIndex);
96
+        }
97
+    }
98
+
99
+    useXboxLayout(padIndex: number) {
100
+        padIndex = padIndex || 1;
101
+        this.pad = this.game.input.gamepad['pad' + padIndex];
102
+        this.moveXAxis = Phaser.Gamepad.XBOX360_STICK_LEFT_X;
103
+        this.moveYAxis = Phaser.Gamepad.XBOX360_STICK_LEFT_Y;
104
+        this.shootXAxis = Phaser.Gamepad.XBOX360_STICK_RIGHT_X;
105
+        this.shootYAxis = Phaser.Gamepad.XBOX360_STICK_RIGHT_Y;
106
+        localStorage.setItem('gamepad', padIndex.toString());
107
+        localStorage.setItem('gamepad' + padIndex + '.layout', 'xbox');
108
+    }
109
+
110
+    useCustomGamepadLayout(padIndex: number) {
111
+        padIndex = padIndex || 1;
112
+        this.pad = this.game.input.gamepad['pad' + padIndex];
113
+        this.moveXAxis = this.readNumberFromLocalStorage('gamepad' + padIndex + '.layout.custom.moveXAxis', Phaser.Gamepad.XBOX360_STICK_LEFT_X);
114
+        this.moveYAxis = this.readNumberFromLocalStorage('gamepad' + padIndex + '.layout.custom.moveYAxis', Phaser.Gamepad.XBOX360_STICK_LEFT_Y);
115
+        this.shootXAxis = this.readNumberFromLocalStorage('gamepad' + padIndex + '.layout.custom.shootXAxis', Phaser.Gamepad.XBOX360_STICK_RIGHT_X);
116
+        this.shootYAxis = this.readNumberFromLocalStorage('gamepad' + padIndex + '.layout.custom.shootYAxis', Phaser.Gamepad.XBOX360_STICK_RIGHT_Y);
117
+        localStorage.setItem('gamepad', padIndex.toString());
118
+        localStorage.setItem('gamepad' + padIndex + '.layout', 'custom');
119
+    }
120
+
121
+    readNumberFromLocalStorage(key: string, defaultValue: number) {
122
+        let i = parseInt(localStorage.getItem(key));
123
+        if (isNaN(i)) {
124
+            return defaultValue;
125
+        } else {
126
+            return i;
127
+        }
128
+    }
129
+
89 130
     shootingAngle(shooterX: number, shooterY: number): number {
90 131
         return this.firstNonNull(this.shootingAngleFromPointer(shooterX, shooterY)
91 132
             , this.shootingAngleFromPad(), this.shootingFromKeyboard());
... ...
@@ -110,8 +151,8 @@ export class Controls {
110 151
     }
111 152
 
112 153
     private shootingAngleFromPad(): number {
113
-        let dx = this.pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_X);
114
-        let dy = this.pad.axis(Phaser.Gamepad.XBOX360_STICK_RIGHT_Y);
154
+        let dx = this.pad.axis(this.shootXAxis);
155
+        let dy = this.pad.axis(this.shootYAxis);
115 156
         dx = Math.abs(dx) <= this.pad.deadZone ? 0 : dx;
116 157
         dy = Math.abs(dy) <= this.pad.deadZone ? 0 : dy;
117 158
         if (dx != 0 || dy != 0) {
... ...
@@ -142,25 +183,21 @@ export class Controls {
142 183
     }
143 184
 
144 185
     isGoingUp(): boolean {
145
-        return this.pad.isDown(Phaser.Gamepad.XBOX360_DPAD_UP)
146
-            || this.pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) < -this.pad.deadZone
186
+        return this.pad.axis(this.moveYAxis) < -this.pad.deadZone
147 187
             || this.kb.isDown(this.keyCodeMoveUp);
148 188
     }
149 189
     isGoingDown(): boolean {
150
-        return this.pad.isDown(Phaser.Gamepad.XBOX360_DPAD_DOWN)
151
-            || this.pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_Y) > this.pad.deadZone
190
+        return this.pad.axis(this.moveYAxis) > this.pad.deadZone
152 191
             || this.kb.isDown(this.keyCodeMoveDown);
153 192
     }
154 193
 
155 194
     isGoingLeft(): boolean {
156
-        return this.pad.isDown(Phaser.Gamepad.XBOX360_DPAD_LEFT)
157
-            || this.pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) < -this.pad.deadZone
195
+        return this.pad.axis(this.moveXAxis) < -this.pad.deadZone
158 196
             || this.kb.isDown(this.keyCodeMoveLeft);
159 197
     }
160 198
 
161 199
     isGoingRight(): boolean {
162
-        return this.pad.isDown(Phaser.Gamepad.XBOX360_DPAD_RIGHT)
163
-            || this.pad.axis(Phaser.Gamepad.XBOX360_STICK_LEFT_X) > this.pad.deadZone
200
+        return this.pad.axis(this.moveXAxis) > this.pad.deadZone
164 201
             || this.kb.isDown(this.keyCodeMoveRight);
165 202
     }
166 203