瀏覽代碼

Simply main game controller

Thomas Dy 7 年之前
父節點
當前提交
295041ed9f
共有 6 個文件被更改,包括 209 次插入131 次删除
  1. 19 122
      src/game.ts
  2. 84 0
      src/game/loading.ts
  3. 29 8
      src/game/select.ts
  4. 40 0
      src/game/typing.ts
  5. 1 1
      src/index.ts
  6. 36 0
      src/util.ts

+ 19 - 122
src/game.ts

@@ -1,155 +1,52 @@
 /// <reference path="level.ts" />
 /// <reference path="audio.ts" />
-/// <reference path="display.ts" />
 /// <reference path="background.ts" />
-/// <reference path="select.ts" />
 /// <reference path="util.ts" />
+/// <reference path="game/loading.ts" />
 
 namespace game {
-  enum GameState {
-    LOADING,
-    SELECT,
-    PLAYING
-  }
-
   interface GameSounds {
     selectSound: audio.Track,
     decideSound: audio.Track
   }
 
-  class LoadingScreen {
-    controller: MainController;
-
-    constructor(controller: MainController) {
-      this.controller = controller;
-    }
-
-    load(): void {
-      console.log('Loading assets...');
-      let configUrl = this.controller.configUrl;
-      let configPromise;
-      if (configUrl.endsWith('.json')) {
-        configPromise = level.loadFromJson(configUrl);
-      } else {
-        configPromise = level.loadFromTM(configUrl);
-      }
-      configPromise.then(config => {
-        this.controller.config = config;
-        this.loadAssets();
-      })
-    }
-
-    loadAssets(): void {
-      let config = this.controller.config;
-
-      Promise.all([
-        this.loadImage(config.background),
-        this.loadTrack(config.selectSound),
-        this.loadTrack(config.decideSound)
-      ]).then(v => {
-        console.log('Loaded assets.');
-        let [background, selectSound, decideSound] = v;
-        this.controller.assets = {
-          selectSound,
-          decideSound
-        }
-        this.finishLoading();
-      })
-    }
-
-    finishLoading(): void {
-      this.controller.bgManager.setBackground(this.controller.config.background);
-      let loadingElement = this.controller.container.querySelector('#loading');
-      loadingElement.addEventListener('transitionend', (event) => this.controller.onConfigLoad());
-      loadingElement.classList.add('finished');
-    }
-
-    loadTrack(url: string): Promise<audio.Track> {
-      if (url == null) {
-        return Promise.resolve(null);
-      } else {
-        return this.controller.audioManager.loadTrack(url);
-      }
-    }
-
-    loadImage(url: string): Promise<void> {
-      if (url.includes('.')) {
-        return new Promise((resolve, reject) => {
-          let image = new Image();
-          image.onload = (event) => resolve();
-          image.src = url;
-        });
-      } else {
-        return Promise.resolve();
-      }
-    }
+  export interface Screen {
+    readonly name: string;
+    handleInput(key: string): void;
+    enter(): void;
+    exit(): void;
   }
 
   export class MainController {
-    container: HTMLElement;
-    configUrl: string;
     config: level.Config | null;
     audioManager: audio.AudioManager;
     bgManager: background.BackgroundManager;
     assets: GameSounds | null;
-    state: GameState;
-    selectScreen: SelectScreen | null;
-    gameController: display.LevelController | null;
+    activeScreen: Screen | null = null;
 
-    constructor(container: HTMLElement, configUrl: string) {
-      this.container = container;
-      this.configUrl = configUrl;
+    constructor(readonly container: HTMLElement, readonly configUrl: string) {
       this.audioManager = new audio.AudioManager();
       this.bgManager = new background.BackgroundManager(container.querySelector('#background'));
-      this.state = GameState.LOADING;
 
       document.addEventListener('keydown', (event) => {
         if (!event.ctrlKey && !event.metaKey) {
-          if (this.state === GameState.SELECT) {
-            this.selectScreen.handleInput(event.key);
-          } else if (this.state === GameState.PLAYING) {
-            if (event.key === 'Escape') {
-              this.onBackToSelect();
-            } else {
-              this.gameController.handleInput(event.key);
-            }
-          }
+          this.activeScreen.handleInput(event.key);
         }
       });
     }
 
-    start(): void {
-      this.container.classList.add('loading');
-      let loadingScreen = new LoadingScreen(this);
-      loadingScreen.load();
-    }
-
-    onConfigLoad(): void {
-      let config = this.config;
-      this.container.style.setProperty('--base-color', config.baseColor);
-      this.container.style.setProperty('--highlight-color', config.highlightColor);
-
-      this.selectScreen = new SelectScreen(this);
-      this.container.classList.remove('loading');
-      this.container.classList.add('select');
-      this.state = GameState.SELECT;
-    }
-
-    onSongSelect(level: level.Level): void {
-      this.container.classList.remove('select');
-      this.container.classList.add('game');
-      this.gameController = new display.LevelController(this.audioManager, level);
-      let gameContainer = this.container.querySelector('#game');
-      util.clearChildren(gameContainer);
-      gameContainer.appendChild(this.gameController.element);
-      this.state = GameState.PLAYING;
+    switchScreen(nextScreen: Screen): void {
+      if (this.activeScreen != null) {
+        this.container.classList.remove(this.activeScreen.name);
+        this.activeScreen.exit();
+      }
+      this.activeScreen = nextScreen;
+      this.activeScreen.enter();
+      this.container.classList.add(this.activeScreen.name);
     }
 
-    onBackToSelect(): void {
-      this.container.classList.remove('game');
-      this.container.classList.add('select');
-      this.gameController.destroy();
-      this.state = GameState.SELECT;
+    start(): void {
+      this.switchScreen(new LoadingScreen(this));
     }
   }
 }

+ 84 - 0
src/game/loading.ts

@@ -0,0 +1,84 @@
+/// <reference path="select.ts" />
+/// <reference path="../game.ts" />
+
+namespace game {
+  export class LoadingScreen implements Screen {
+    readonly name: string = 'loading';
+
+    constructor(private controller: MainController) {}
+
+    enter(): void {
+      console.log('Loading assets...');
+      let configUrl = this.controller.configUrl;
+      let configPromise;
+      if (configUrl.endsWith('.json')) {
+        configPromise = level.loadFromJson(configUrl);
+      } else {
+        configPromise = level.loadFromTM(configUrl);
+      }
+      configPromise.then(config => {
+        this.controller.config = config;
+        this.loadAssets();
+      })
+    }
+
+    loadAssets(): void {
+      let config = this.controller.config;
+
+      Promise.all([
+        this.loadImage(config.background),
+        this.loadTrack(config.selectSound),
+        this.loadTrack(config.decideSound)
+      ]).then(v => {
+        console.log('Loaded assets.');
+        let [background, selectSound, decideSound] = v;
+        this.controller.assets = {
+          selectSound,
+          decideSound
+        }
+        this.finishLoading();
+      })
+    }
+
+    finishLoading(): void {
+      this.controller.bgManager.setBackground(this.controller.config.background);
+      let loadingElement = this.controller.container.querySelector('#loading');
+      loadingElement.addEventListener('transitionend', (event) => this.switchToSelect());
+      loadingElement.classList.add('finished');
+    }
+
+    loadTrack(url: string): Promise<audio.Track> {
+      if (url == null) {
+        return Promise.resolve(null);
+      } else {
+        return this.controller.audioManager.loadTrack(url);
+      }
+    }
+
+    loadImage(url: string): Promise<void> {
+      if (url.includes('.')) {
+        return new Promise((resolve, reject) => {
+          let image = new Image();
+          image.onload = (event) => resolve();
+          image.src = url;
+        });
+      } else {
+        return Promise.resolve();
+      }
+    }
+
+    switchToSelect(): void {
+      let selectScreen = new SelectScreen(this.controller);
+      this.controller.switchScreen(selectScreen);
+    }
+
+    handleInput(key: string): void {}
+
+    exit(): void {
+      let config = this.controller.config;
+      let containerStyle = this.controller.container.style;
+      containerStyle.setProperty('--base-color', config.baseColor);
+      containerStyle.setProperty('--highlight-color', config.highlightColor);
+    }
+  }
+}

+ 29 - 8
src/select.ts → src/game/select.ts

@@ -1,9 +1,10 @@
-/// <reference path="game.ts" />
-/// <reference path="util.ts" />
+/// <reference path="typing.ts" />
+/// <reference path="../game.ts" />
+/// <reference path="../util.ts" />
 
 namespace game {
-  export class SelectScreen {
-    controller: MainController;
+  export class SelectScreen implements Screen {
+    readonly name: string = 'select';
     folderInfo: HTMLElement;
     songInfo: HTMLElement;
     songList: HTMLElement;
@@ -24,7 +25,7 @@ namespace game {
       return this.listControllers[this.currentFolderIndex];
     }
 
-    constructor(controller: MainController) {
+    constructor(private controller: MainController) {
       this.controller = controller;
       let container = controller.container;
       this.folderInfo = container.querySelector('#folder-info');
@@ -52,6 +53,10 @@ namespace game {
       this.init = false;
     }
 
+    enter(): void {
+      this.folderController.listeners.attach();
+    }
+
     handleInput(key: string): void {
       this.activeListController.handleInput(key);
       this.folderController.handleInput(key);
@@ -68,7 +73,9 @@ namespace game {
 
     chooseSong(index: number): void {
       this.controller.assets.decideSound.play();
-      this.controller.onSongSelect(this.currentLevelSet.levels[index]);
+      let level = this.currentLevelSet.levels[index];
+      let gameScreen = new game.TypingScreen(this.controller, level, this);
+      this.controller.switchScreen(gameScreen);
     }
 
     selectLevelSet(index: number): void {
@@ -77,6 +84,10 @@ namespace game {
       this.songList.appendChild(this.activeListController.element);
       this.selectSong(this.activeListController.currentIndex);
     }
+
+    exit(): void {
+      this.folderController.listeners.detach();
+    }
   }
 
   class FolderSelectController {
@@ -84,15 +95,25 @@ namespace game {
     levelSets: level.LevelSet[];
     currentIndex: number;
     onFolderChange: (index: number) => void;
+    listeners: util.ListenersManager;
 
     constructor(element: HTMLElement, levelSets: level.LevelSet[], onFolderChange: (index: number) => void) {
       this.labelElement = element.querySelector('.label');
       this.levelSets = levelSets;
       this.currentIndex = 0;
       this.onFolderChange = onFolderChange;
+      this.listeners = new util.ListenersManager();
+      this.listeners.add(
+        element.querySelector('.left'),
+        'click',
+        () => this.scroll(-1)
+      );
+      this.listeners.add(
+        element.querySelector('.right'),
+        'click',
+        () => this.scroll(1)
+      );
 
-      element.querySelector('.left').addEventListener('click', () => this.scroll(-1));
-      element.querySelector('.right').addEventListener('click', () => this.scroll(1));
       this.scroll(0);
     }
 

+ 40 - 0
src/game/typing.ts

@@ -0,0 +1,40 @@
+/// <reference path="../display.ts" />
+/// <reference path="../game.ts" />
+
+namespace game {
+  import Level = level.Level;
+
+  export class TypingScreen implements Screen {
+    readonly name: string = 'game';
+    gameController: display.LevelController;
+
+    constructor(
+      readonly controller: MainController,
+      readonly level: Level,
+      readonly prevScreen: Screen
+    ) {}
+
+    enter(): void {
+      let gameContainer = this.controller.container.querySelector('#game');
+      util.clearChildren(gameContainer);
+      this.gameController = new display.LevelController(this.controller.audioManager, this.level);
+      gameContainer.appendChild(this.gameController.element);
+    }
+
+    handleInput(key: string): void {
+      if (key === 'Escape') {
+        this.returnToSelect();
+      } else {
+        this.gameController.handleInput(key);
+      }
+    }
+
+    returnToSelect(): void {
+      this.controller.switchScreen(this.prevScreen);
+    }
+
+    exit(): void {
+      this.gameController.destroy();
+    }
+  }
+}

+ 1 - 1
src/index.ts

@@ -1,5 +1,5 @@
 /// <reference path="game.ts" />
 
 let container: HTMLElement = document.querySelector('#container');
-let controller = new game.MainController(container, 'tm');
+let controller = new game.MainController(container, 'levels.json');
 controller.start();

+ 36 - 0
src/util.ts

@@ -9,4 +9,40 @@ namespace util {
       node.removeChild(node.lastChild);
     }
   }
+
+  class ListenerManager {
+    constructor(
+      private target: EventTarget,
+      private event: string,
+      private handler: EventListener
+    ) {}
+
+    attach(): void {
+      this.target.addEventListener(this.event, this.handler);
+    }
+
+    detach(): void {
+      this.target.removeEventListener(this.event, this.handler);
+    }
+  }
+
+  export class ListenersManager {
+    private listeners: ListenerManager[] = [];
+
+    add(target: EventTarget, event: string, handler: EventListener, attach: boolean = true): void {
+      let listener = new ListenerManager(target, event, handler);
+      this.listeners.push(listener);
+      if (attach) {
+        listener.attach();
+      }
+    }
+
+    attach(): void {
+      this.listeners.forEach(l => l.attach());
+    }
+
+    detach(): void {
+      this.listeners.forEach(l => l.detach());
+    }
+  }
 }