鱼妈妈与鱼宝宝都是鱼,所以我们可以抽象一些它们相同的部分,这里又俩种方式,一种是通过函数去创建,也就是鱼厂,这个是面向过程编程。此时我们写面向对象代码,使用继承来减少代码。

    因为鱼宝宝是要跟着鱼妈妈的,所以我们把鱼妈妈跟着鼠标的坐标改一下就能用了。

    修改一下鱼宝宝的初始坐标。因为 fish-mother.ts 文件要放鱼妈妈和鱼宝宝,所以叫个个名字就不合适了,我们把它改成 fish.ts ,所以下面代码写到 fish.ts 里面。

    1. import { ctx_one, cvs_height, cvs_width, mouse_x, mouse_y, fish_mother } from "./init";
    2. import utils from "./utils";
    3. // 鱼妈妈
    4. class FishMother{
    5. x: number = cvs_width / 2; // 坐标轴 x
    6. y: number = cvs_height / 2 ; // 坐标轴 y
    7. bigEye = new Image(); // 眼睛
    8. bigBody = new Image(); // 身体
    9. BigTail = new Image(); // 尾巴
    10. angle: number = 0; // 鱼的角度
    11. constructor(){
    12. this.bigEye.src = 'assets/img/bigEye0.png';
    13. this.bigBody.src = 'assets/img/bigSwim0.png';
    14. this.BigTail.src ='assets/img/bigTail0.png';
    15. }
    16. draw(){
    17. this.x = utils.lerpDistance(mouse_x, this.x , .95)
    18. this.y = utils.lerpDistance(mouse_y, this.y , .95)
    19. let instance_X = mouse_x - this.x; // 边 a
    20. let instance_Y = mouse_y - this.y; // 边 b
    21. let ag = Math.atan2(instance_Y, instance_X) + Math.PI // [-PI, PI]
    22. this.angle = utils.lerpAngle(ag, this.angle, .9)
    23. ctx_one.save();
    24. ctx_one.translate(this.x, this.y); // 定义相对定位的坐标中心点
    25. ctx_one.rotate(this.angle);
    26. ctx_one.scale(.8, .8);
    27. ctx_one.drawImage(this.BigTail, -this.BigTail.width / 2 + 30, -this.BigTail.height / 2); // 这里的尾巴,往右移动30像素,让它在身体的后面。
    28. ctx_one.drawImage(this.bigBody, -this.bigBody.width / 2, -this.bigBody.height / 2);
    29. ctx_one.drawImage(this.bigEye, -this.bigEye.width / 2, -this.bigEye.height / 2); // 居中,所以向左移动宽度的一半,向上移动宽度的一半
    30. ctx_one.restore();
    31. }
    32. }
    33. // 鱼宝宝
    34. class FishBaby extends FishMother {
    35. x: number = cvs_width / 2 + 50; // 坐标轴 x
    36. y: number = cvs_height / 2 + 50; // 坐标轴 y
    37. constructor() {
    38. super()
    39. this.bigEye.src = 'assets/img/babyEye0.png';
    40. this.bigBody.src = 'assets/img/babyFade0.png';
    41. this.BigTail.src = 'assets/img/babyTail0.png';
    42. }
    43. draw(){
    44. this.x = utils.lerpDistance(fish_mother.x, this.x , .98)
    45. this.y = utils.lerpDistance(fish_mother.y, this.y , .98)
    46. let instance_X = fish_mother.x - this.x; // 边 a
    47. let instance_Y = fish_mother.y - this.y; // 边 b
    48. let ag = Math.atan2(instance_Y, instance_X) + Math.PI // [-PI, PI]
    49. this.angle = utils.lerpAngle(ag, this.angle, .7)
    50. ctx_one.save();
    51. ctx_one.translate(this.x, this.y); // 定义相对定位的坐标中心点
    52. ctx_one.rotate(this.angle);
    53. ctx_one.scale(.8, .8);
    54. ctx_one.drawImage(this.BigTail, -this.BigTail.width / 2 + 30, -this.BigTail.height / 2); // 这里的尾巴,往右移动30像素,让它在身体的后面。
    55. ctx_one.drawImage(this.bigBody, -this.bigBody.width / 2, -this.bigBody.height / 2);
    56. ctx_one.drawImage(this.bigEye, -this.bigEye.width / 2, -this.bigEye.height / 2); // 居中,所以向左移动宽度的一半,向上移动宽度的一半
    57. ctx_one.restore();
    58. }
    59. }
    60. export {
    61. FishMother,
    62. FishBaby
    63. }

    因为修改了文件名和导入导出,所以我们还需要修改一下其他模块的代码。比如 init.ts 文件,我们需要在 init 里面初始化鱼宝宝。

    1. import Anemones from "./anemones";
    2. import Fruits from "./fruits";
    3. import { FishMother, FishBaby } from "./fish";
    4. /****************************/
    5. /** 初始化所有需要初始化的变量 **/
    6. /****************************/
    7. let cvs_one: HTMLCanvasElement,
    8. cvs_two: HTMLCanvasElement,
    9. ctx_one: CanvasRenderingContext2D,
    10. ctx_two: CanvasRenderingContext2D;
    11. let mouse_x: number, // 鱼的x坐标,和鼠标x的坐标
    12. mouse_y: number; // 鱼的y坐标,和鼠标y的坐标,因为鼠标在哪鱼在哪,所以重合。
    13. let cvs_width: number,
    14. cvs_height: number;
    15. let anemones: Anemones , fruits: Fruits, fish_mother: FishMother, fish_baby: FishBaby;
    16. const bgPic = new Image();
    17. function getCanvasAndContextById(id: string): [HTMLCanvasElement, CanvasRenderingContext2D] {
    18. const dom = <HTMLCanvasElement>document.querySelector('#' + id);
    19. const ctx = dom.getContext('2d');
    20. return [dom, ctx];
    21. }
    22. // 鼠标移动监听函数
    23. function mouseMove(e: MouseEvent) {
    24. // offset = layer + 1
    25. if(e.offsetX || e.layerX) {
    26. mouse_x = typeof e.offsetX == undefined ? e.layerX : e.offsetX
    27. mouse_y = typeof e.offsetY == undefined ? e.layerY : e.offsetY
    28. }
    29. }
    30. function init() {
    31. [cvs_one, ctx_one] = getCanvasAndContextById('one');
    32. [cvs_two, ctx_two] = getCanvasAndContextById('two');
    33. bgPic.src = 'assets/img/background.jpg';
    34. cvs_width = cvs_one.width;
    35. cvs_height = cvs_one.height;
    36. anemones = new Anemones()
    37. fruits = new Fruits()
    38. fish_mother = new FishMother()
    39. fish_baby = new FishBaby()
    40. mouse_x = cvs_width / 2; // 先把鱼初始化在画布的中间
    41. mouse_y = cvs_height / 2;
    42. // 因为鱼是在 canvas one 上面,所以把监听添加到 one 上面
    43. cvs_one.addEventListener('mousemove', mouseMove, false);
    44. }
    45. export default init;
    46. export {
    47. bgPic,
    48. cvs_width,
    49. cvs_height,
    50. cvs_one,
    51. cvs_two,
    52. ctx_one,
    53. ctx_two,
    54. anemones,
    55. fruits,
    56. fish_mother,
    57. fish_baby,
    58. mouse_x,
    59. mouse_y
    60. };

    在 game-loop.ts 里面画鱼宝宝。

    1. import { bgPic, cvs_width , cvs_height, ctx_two, ctx_one, anemones, fruits, fish_mother, fish_baby } from "./init";
    2. import utils from "./utils";
    3. let lastTime: number = Date.now(), // 记录上一次绘制的时间
    4. deltaTime: number = 0; // requestAnimationFrame 执行完成所用的时间 = 当前时间 - 上一次绘制的世界
    5. /**
    6. * 鱼妈妈与果实的碰撞检测
    7. */
    8. function fishAndFruitsCollision() {
    9. for (let i = fruits.num; i >= 0; i--) {
    10. // 假如或者就计算鱼儿与果实的距离
    11. if(fruits.alive[i]) {
    12. // 得到距离的平方根
    13. const distance = utils.getDistance(
    14. {x: fruits.x[i], y: fruits.y[i]},
    15. {x: fish_mother.x, y: fish_mother.y}
    16. );
    17. // 假如距离小于 500 让它死亡
    18. if(distance < 500) {
    19. fruits.dead(i)
    20. }
    21. }
    22. }
    23. }
    24. function gameLoop() {
    25. const now = Date.now()
    26. deltaTime = now - lastTime;
    27. lastTime = now;
    28. // 给 deltaTime 设置上线
    29. if(deltaTime > 40) deltaTime = 40;
    30. // console.log(deltaTime);
    31. drawBackbround() // 画背景图片
    32. anemones.draw() // 海葵绘制
    33. fruits.draw() // 果实绘制
    34. fruits.monitor() // 监视果实,让死去的果实得到新生
    35. ctx_one.clearRect(0, 0, cvs_width, cvs_width); // 清除掉所有,再进行绘制,要不然的话会多次绘制而进行重叠。
    36. fish_mother.draw() // 绘制鱼妈妈
    37. fish_baby.draw() // 绘制鱼宝宝
    38. fishAndFruitsCollision() // 每一帧都进行碰撞检测
    39. requestAnimationFrame(gameLoop); // 不断的循环 gameLoop,且流畅性提升
    40. }
    41. function drawBackbround() {
    42. ctx_two.drawImage(bgPic, 0, 0, cvs_width, cvs_height)
    43. }
    44. export { deltaTime }
    45. export default gameLoop;

    鱼宝宝已经绘制完毕啦。