「小游戏系列」之简简单单的益智拼图-ts的应用

593 阅读3分钟

我正在参加掘金社区游戏创意投稿大赛团队赛,详情请看:游戏创意投稿大赛

我也是个临时抱佛脚的,对 vue 和 ts 都不熟,借此机会也算学习一把,做个益智拼图小游戏。团队是 大帅 带队,永恒君 主架构,我做的部分倒不多,所以本篇主要用于记录开发过程中遇到的技术点。

ts Partial<T>

先上一道 ts 方面的前菜:

interface GameConfig {
  fps: number;
  canvasWidth: number;
  canvasHeight: number;
  initialized: boolean;
  pause: boolean;
}
class Config implements GameConfig {
  fps: number = 60;
  canvasWidth: number = 0;
  canvasHeight: number = 0;
  initialized: boolean = false;
  pause: boolean = false;
  constructor(config = {}) {
    Object.keys(config).forEach(key => {
      this[key] = config[key];
    });
  }
}

代码如上,由于 GameConfig 作为接口肯定不能把字段改为 ?:,但传入的 config 显然是需要约束和提示的。直接约定 config: GameConfig 肯定不行,不然每个属性都得必传了。再写一堆带 ?: 的约定也不现实。

后来找到 config: Partial<GameConfig> 这样的处理,这个 Partial<T> 就很巧妙,给这个对象自动加上了 ?:,相信它在实战中会很有用。

class Config implements GameConfig {
  constructor(config:  Partial<GameConfig> = {}) {}
}

new Config(); // 不报错
new Config({}); // 不报错
new Config({ f }); // 有提示 fps?
new Config({ xx: 1 }); // 报错 not exist in type

ts 范式

在写容器 Stage 类时,永恒君说由于项目简单要偷个懒,所以直接把子组件写了出来而没有进一步抽象,但 addElementByKey 这里的类型约定就让我头大起来了。

interface StageConfig = {
  background: StageBackground;
  container: PuzzleContainer;
  idle: IdleContainer;
  items: PuzzleItem[];
}
class Stage implements StageConfig {
  addElementByKey(key, element) {}
}

显然 keyelement 的类型是要对应的才行,不然就无法避免错误了。假设 key: keyof StageConfig,那 element 理应是 StageConfig[key] 相对应的,好像也只能用范式来解决它了。

不得不说,范式还是理解起来挺有难度的,试了挺多才得到下面的结果。

type setByKey<T> = <K extends keyof T>(key: K, value: T[K]) => void;

另外函数约定使用起来也容易和箭头函数混淆,让我一度怀疑是不是我理解错误。

class Stage implements StageConfig {
  addElementByKey: setByKey<StageConfig> = (key, element) => {}
}

动画 easing

以前写动画都是 (v2 - v1) * progress + v1 这样的写法,这次想试试加上动画 easing 的逻辑,主要借鉴 jquery.easing 的思路。

传入四个值,已运行时间 t,起始量 b,需变动总量 c,需变动总时长 d,然后就可以套用一些函数了。

type EasingType = (t: number, b: number, c: number, d: number) => number;

const easeLinear: EasingType = (t, b, c, d) => {
  return c * t / d + b;
}

const easeInQuad : EasingType = (t, b, c, d) => {
  return c * (t /= d) * t + b;
}

动画类也很好写,只是此处的 startnumber 类型,后续可进行抽象为接口,重写 startupdate 即可实现比如颜色、数组等其他类型的动画效果。

type MoreOption = {
  easing?: string,
  onProgress?: (result: any, progress: number) => void,
  onFinish?: () => void,
};
class Animation {
  start: number = 0;
  end: number = 0;
  duration: number = 1000;
  easing?: string = 'easeLinear';
  _time: number = 0; // 本类创建时的时间
  done: boolean = false;
  constructor(start: number, end: number, duration = 1000, options?: MoreOption | VoidFunction) {}
  update(now: number) {
    const t = now - this._time;
    const b = this.start;
    const c = this.end - this.start;
    const d = this.duration;
  }
}

代码不复杂,但还是感谢这次实战中的学习,以及大佬们的指教。

image.png

image.png

Github: github.com/forever-z-1…