两天入门,cocos开发飞机大作战

点击小卡片
03dc63801b64f99be1ad7d43121078ff.png记录你的专属 收藏ff0b2c1eb3ca558e596ec8688a3ada1f.png

6c6d88a281a15b575b0f7d2950bc92db.png

效果与预览

9fc74e92d3661f8886b1d5afc8d86f20.jpeg

手机扫码体验:

ae04edab9765f2d7fd30d540a19a8651.png
下载

Cocos官网:https://www.cocos.com

简介

Cocos是一款游戏开发引擎,提供了一套完整的游戏开发解决方案,包括渲染引擎、物理引擎、UI编辑器、动画编辑器等,支持多种开发语言并且可以发布到不同的平台。

刚开始接触,看着互联网上各种版本的资料,一头雾水,好几个版本究竟该学哪个用哪个,都有啥区别。

随着Cocos的发展,出现了不同的版本,Cocos2d-x,Cocos2d-js,Cocos Creator

Cocos2d-jsCocos2d-x的子集,Cocos2d-x支持 C++、Javascript 及 Lua 三种语言来进行游戏开发,并且支持所有常见平台,包括 iOS、Android、Windows、macOS、Linux。

Cocos Creator 3.x底层使用全新3D内核重写,是当前Cocos Creator最新版本,与2.x版本的api不完全兼容,存在很多差异,这也导致查看网络上很多教程的时候使用2.x在3.x中无法调用。

Cocos Creator 2.x底层基于Cocos2d-x的精简版本,已经停止更新。

特性Cocos2d-xCocos Creator
编程语言C++, Lua, JavaScriptJavaScript, TypeScript
平台支持iOS, Android, Windows, macOS, Linux, HTML5iOS, Android, Windows, macOS, Linux, HTML5
渲染引擎Cocos2d-x 自带渲染引擎Cocos2d-x-lite, Cocos3D
UI编辑器Cocos Studio (已停止更新)Cocos Creator 内置 UI 编辑器
动画编辑器Cocos Studio (已停止更新)Cocos Creator 内置动画编辑器
状态发布于 2010 年,2019 年停止更新发布于 2021 年初,3.x是当前 Cocos Creator 的最新版本

总结下来,作为初学者,希望快速构建游戏,学习Cocos Creator3.x就行了。

基础概念

官网软件下载并安装:https://www.cocos.com/creator-download api文档:https://docs.cocos.com/creator/api/zh/

场景(Scene)

一个游戏会分为多个场景,场景可以被理解为游戏的一个"舞台"或者"房间"。它是在游戏中创建、编辑和组织游戏对象(如角色、背景、道具等)的地方。

🤔作为一个前端开发,这不就是页面吗,往页面(场景)里添加组件(角色、背景、道具)内容,然后在不同的页面(场景)中跳转路由(切换场景)。

导演(Director)

导演director实例化单例负责控制游戏的整体流程和状态,可以把它想象成一个电影导演,负责指挥游戏中的各个部分,确保游戏按照预期的方式运行,例如切换场景,暂停游戏,获取场景等。

举个例子加载场景:director.loadScene('index'),加载index场景。

节点(Node)

节点是场景中的基本单位,它代表一个游戏对象,如角色、道具等。节点可以包含其他节点,形成一个树形结构。节点具有位置、缩放、旋转等属性,可以通过改变这些属性来控制游戏对象的表现。

🤔DOM元素

预制件(Prefab)

预制件是预先设计好的游戏对象模板,可以在场景中快速创建和复制。预制件包含了节点、组件和资源的配置信息,方便在游戏中重复使用。

🤔有点像前端中的组件模版,把写好的内容抽离成组件,下次直接使用这个已经实现的组件,而不是再次cv复制一次该节点。

组件(Component)

组件是附加在节点上的脚本或功能模块,用于定义节点的行为。例如,一个角色节点可以附加一个移动组件来控制角色的移动。组件之间可以相互通信,实现复杂的游戏逻辑。

🤔在cocos中,ts脚本也是一个Component,例如新建一个ts控制器,添加给这个角色后就可以通过这个ts脚本去控制这个角色。

资源(Asset)

资源是游戏中使用的各种素材,如图片、音频、字体等。资源可以在编辑器中导入、管理和分配给节点、组件等。

编辑器

官方文档:https://docs.cocos.com/creator/manual/zh/editor/

官方文档介绍比较详细了,简单了解一下

0006d55809cc6eb318f78208b5b48de5.png
image-20230921175030086

飞机大作战开始

静态资源下载

链接: https://pan.baidu.com/s/16q28nFUspnYOOdWqVNYMiA?pwd=32jc 提取码: 32jc

前置准备

新建项目

使用安装好的Cocos Dashboard新建一个2D空项目

加载资源

相关文档地址:https://docs.cocos.com/creator/manual/zh/asset/dynamic-load-resources.html

使用动态加载资源,动态加载资源指的是在游戏运行过程中按需加载和卸载资源,而不是在游戏开始时一次性加载所有资源。这种方式可以有效地减少游戏的启动时间和内存占用,提高游戏性能。

值得注意的是动态加载的资源「必须」要放在assets/resources文件夹中,默认是没有该文件夹,需要手动创建,该文件夹中的资源可以通过resources相关的api进行操作。

将需要的资源移动到resources

cef11489da1bf972dbafaf02dd62da48.png
image-20230921174048323
调整分辨率

背景图片大小是480*852,新建一个480*800的画布。

windows调整路径:顶部菜单栏 Project->Project Settings,Mac电脑直接点击如图框选位置。

这里避免黑边问题,可以将Fit Width取消勾选,并勾选Fit Hight,以高度优先。

f3040acb38b769b349e2b66e66ced015.png

背景循环移动

期望实现背景从上往下不断移动,看上去像是飞机再往前移动,要实现图片滚动,可以使用两张相同的图片,同时向下移动,当底部图片滚动出屏幕时,设置位置到顶部,依次循环即可实现。

  1. 我们可以在Canvas下新建一个Node节点并拖入两张背景图片,并设置好两张图片坐标刚好在上下位置,为了方便管理,可以对其进行重命名。

3baed0ef2f7e303e545f358057110fbe.png
image-20230922141624763
  1. 实现滚动脚本

当前的背景是静态不可动的,为了实现背景滚动,需要给Node节点bg添加一个TS脚本,并用脚本控制该节点下的所有背景图片向下移动和回到顶部。

(1)新建一个TS脚本assets/script/BgControl

(2)在bg节点中添加Component,可以点击bg在右侧「属性检查器」中点击Add Component选择BgControl,也可以直接将BgControl拖入到bg右侧「属性检查器」中。

10f8fb53ee648e555aa0ceadeae4698c.png
image-20230921190106610

(3)编写BgControl文件,通过读取bg节点的子节点,在每帧调用生命周期中循环执行子节点也就是背景图片向下移动。

打开BgControl文件可以看到存在两个生命周期方法startupdatestart在初始化的时候执行一次,update会在每一帧执行,用于更新对象的状态,背景移动就需要在update中编写。

export class BgControl extends Component {
    start() {
    }
    update(deltaTime: number) {
    }
}

生命周期回调介绍:https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html

Node相关api文档:https://docs.cocos.com/creator/3.8/api/zh/class/Node

通过循环获取当前节点下的子节点,并且拿到坐标值,将y轴坐标减去固定值,当这个值已经到底了,重新设置到顶部,新的轮回开始。

相关的api,例如获取子节点,获取坐标信息,这些需要在api文档上查询

「完整代码:」

import { _decorator, Component, director, Node } from "cc";
const { ccclass, property } = _decorator;

@ccclass("BgControl")
export class BgControl extends Component {
  start() {}

  // 生命周期每帧调用函数
  update(deltaTime: number) {
    // 使用this.node.children获取当前节点下的子节点
    for (let item of this.node.children) {
      // 使用getPosition获取坐标信息
      const { x, y } = item.getPosition();
      // 计算移动坐标
      const moveY = y - 100 * deltaTime;
      item.setPosition(x, moveY);
      // 如果超出屏幕 重新回到顶部,也就是当前位置加上两倍的高度
      if (moveY < -870) {
        item.setPosition(x, moveY + 852 * 2);
      }
    }
  }
}

update方法中的deltaTime参数是一个用于表示游戏中两帧之间的时间差的概念。在游戏开发中,游戏画面通常是由一帧帧图像连续播放组成的,每一帧都包含了游戏中所有元素的状态和位置。为了使游戏运行流畅,通常会尽量让每一帧的播放时间保持一致。

但是,由于计算机性能和系统负载等原因,,实际上每一帧的播放时间可能会有所不同。为了解决这个问题,引入了 deltaTime这个概念。deltaTime 是一个表示两帧之间经过的时间(以秒为单位)的值。通过使用 deltaTime,可以使游戏中的元素运动更加平滑,因为它们的速度会根据实际的时间差进行调整。

例如,如果一个游戏角色需要在 1 秒内移动 100 像素,那么在每一帧中,它的移动距离应该是 100 * deltaTime。这样,即使每一帧的时间不同,角色的移动速度也会根据实际的时间差进行调整,使得在 1 秒内总共移动 100 像素。

如果直接调整 100 这个数值,那么元素的移动速度将会与帧率直接关联。在帧率较高的设备上,元素会移动得更快;而在帧率较低的设备上,元素会移动得更慢。这会导致游戏在不同设备上的表现和玩家体验不一致。

「运行查看效果」

f8b028c0cf15e7e3bbe681cb47e195bf.gif
p3ufm-5s27j

添加飞机

飞机需要实现能够跟随鼠标或者手势按压的位置,并且能够发射子弹。

  1. 拖入飞机图片,并创建一个飞机的控制器/assets/script/PlayerControl,将PlayerControl拖入到飞机右侧「属性检查器」中。

b5ed6fc7ab2c0d4ea5501f8221fd2e86.png

2.实现跟随鼠标功能

「监听节点鼠标移动方法:」

节点事件api:https://docs.cocos.com/creator/manual/zh/engine/event/event-node.html

Node.on(type: Node.EventType, callback: (e:EventTouch)=>void, target?: unknown, useCapture?: any):void

实现跟随鼠标移动,只需要监听Node.EventType.MOUSE_MOVE类型,并在回调中设置飞机到鼠标位置即可。

this.node.on(Node.EventType.TOUCH_MOVE, (e: EventTouch) => {
    console.log("监听到了",e);
 });

callback回调函数回传了一个EventTouch类型参数,可以通过这个参数中的方法获取鼠标移动的位置。

「获取触摸位置」

触摸事件api: https://docs.cocos.com/creator/manual/zh/engine/event/event-api.html

获取触摸位置有两个方法,一个是全局触摸api:getLocation,一个是节点触摸api:getUILocation

使用的时候发现getLocation获取的坐标比getUILocation大一倍,🤔于是看了看官网的介绍,😐好像并没有说清楚两个具体区别

函数名返回值类型单位意义
「getLocation」Vec2设备物理像素获取鼠标位置对象,对象包含 x 和 y 属性。
「getUILocation」Vec2设计分辨率获取当前鼠标在 UI 窗口内相对于左下角的坐标位置,对象包含 x 和 y 属性。

这里两个区别涉及到「设计分辨率」「设备物理像素」两个概念

「设计分辨率」是设计时设置的分辨率大小,「设备物理像素」是显示设备中的最小显示单元,物理像素的数量决定了显示设备的分辨率,分辨率越高,物理像素越多,图像越清晰。

getLocation() 获取坐标时,返回的坐标是基于「设备物理像素」的。如果设备像素比大于 1,那么返回的坐标可能会比设计分辨率的坐标大。

通过window.devicePixelRatio可以获取「设备像素比」,比如我这里获取window.devicePixelRatio结果为2,所以获取的坐标大了一倍,为了解决这个问题,可以将 getLocation() 返回的坐标除以设备像素比,可以获取与设计分辨率一致的坐标。

设置节点有两个类似的方法

setWorldPosition:此方法用于设置节点在世界坐标系中的位置。世界坐标系是一个统一的坐标系,它不随任何父节点的改变而改变。

setPosition:此方法用于设置节点在本地坐标系(相对于父节点)中的位置。本地坐标系是相对于父节点的坐标系,它会随父节点的改变而改变。

最后这里使用getUILocation获取当前鼠标坐标,并且使用setWorldPosition设置给当前节点。

import { _decorator, Component, EventTouch, Node, v3 } from "cc";
const { ccclass, property } = _decorator;

@ccclass("PlayerControl")
export class PlayerControl extends Component {
  start() {
    this.node.on(Node.EventType.TOUCH_MOVE, (e: EventTouch) => {
      const { x, y } = e.getUILocation();
      this.node.setWorldPosition(v3(x, y));
    });
  }

  update(deltaTime: number) {}
}

「运行查看效果」

aa8059b2fdca9c1832ad9fb4d8505659.gif
9rb9j-ej7fh

添加子弹

  1. 拖入子弹到Canvas节点下,并给子弹添加上新建的组件/assets/script/BulletControl

fc9b1fac5b68cbb7dd137a7bb17cbc75.png

  1. 编写子弹内容,设置子弹位置不断往前,很简单的代码,获取坐标,并且y坐标增加。

update(deltaTime: number) {
    const { x, y } = this.node.getPosition();
    this.node.setPosition(x, y + 600 * deltaTime);
  }

「预览效果」

1530b3f20adf59600de3d63bec80780b.gif
7epbv-54mwo
  1. 添加「预制件(Prefab)」

在前面基础概念有提到,子弹里包含ts脚本这些内容,为了创建更加方便,将子弹添加为「预制件」传递给飞机,飞机拿到子弹对象后,创建子弹即可实现发射子弹的效果。

(1)在「层级管理器」中将子弹拖入进/assets/prefab文件夹中

445a29e23792f56a9ef0b293973928fa.png
image-20230922215233194

(2)给飞机添加参数接收子弹对象

import { _decorator, Component, EventTouch, Node, Prefab, v3 } from "cc";
const { ccclass, property } = _decorator;

@ccclass("PlayerControl")
export class PlayerControl extends Component {
  @property(Prefab)
  bullet: Prefab = null;

  start() {
    this.node.on(Node.EventType.TOUCH_MOVE, (e: EventTouch) => {
      const { x, y } = e.getUILocation();
      this.node.setWorldPosition(v3(x, y));
    });
  }

  update(deltaTime: number) {}
}

添加完成后在编辑器中就可以查看到这个属性,将子弹的预制件拖过去后,就可以在飞机类中操作子弹

afcaee741a34ef0da10aa889677b3259.png
image-20230923103351578

(3)使用schedule定时器创建子弹

在cocos中存在schedule定时器的方法,而js也提供有setInterval方法能够实现定时执行,在场景切换、暂停、组件销毁时schedule会自动停止运行,而setInterval不会自动停止,可能会导致内存泄漏或其他问题,所以最好是使用cocos内置的定时器方法。

添加子弹对象有很多种方式,举两个方案

「方案1:」

首先通过api getPosition拿到飞机的位置,通过导演类的方法获取场景下的Canvas,并通过instantiate将子弹实例化成节点之后,添加到Canvas画布中。

start() {
  // ...
    this.schedule(() => {
      const { x, y } = this.node.getPosition();
      // 通过名称获取节点的子节点。
      const Canvas = director.getScene().getChildByName("Canvas");
      const node = instantiate(this.bullet);
      node.setPosition(x, y + 70);
      Canvas.addChild(node);
    }, 0.2);
  }

「方案2:」

直接通过节点下的方法setParent设置子弹的父节点为当前飞机的父节点,也就是和飞机同级。

start() {
  // ...
    this.schedule(() => {
      const { x, y } = this.node.getPosition();
      const node = instantiate(this.bullet);
      node.setParent(this.node.parent);
      node.setPosition(x, y + 70);
    }, 0.2);
  }

🤔用方案2还怪方便的嘿

(4)销毁子弹

在定时器中会不断地创建子弹,超出屏幕之后依然存在,这里需要简单的优化一下,子弹超出屏幕范围就进行销毁。

修改BulletControl

import { _decorator, Component, director, Node, view } from "cc";
const { ccclass, property } = _decorator;

@ccclass("BulletControl")
export class BulletControl extends Component {
  start() {}

  update(deltaTime: number) {
    const { x, y } = this.node.getPosition();
    const moveY = y + 600 * deltaTime;
    this.node.setPosition(x, moveY);
    // 判断超出屏幕销毁子弹
    if(moveY > 800 ) {
      this.node.destroy();
    }
  }
}

添加敌人

添加敌机与添加子弹类似,敌机从屏幕外由一个节点进行管理,完成一个敌机并添加脚本后,添加为预制件,并通过敌机管理节点随机x坐标进行创建。

(1)摆好位置

创建一个enemy空节点,并把airplane敌机拖入空节点中,移动空节点到顶部屏幕外

e044b6938c91b789642006e70517818e.png

(2)创建一个/assets/script/EnemyControl敌机脚本并添加给airplane,修改代码设置敌机不断前进,超出屏幕后进行销毁,与子弹代码类似,只是方向不同

import { _decorator, Component, Node } from "cc";
const { ccclass, property } = _decorator;

@ccclass("EnemyControl")
export class EnemyControl extends Component {
  start() {}

  update(deltaTime: number) {
    const { x, y } = this.node.getPosition();
    const moveY = y - 600 * deltaTime;
    this.node.setPosition(x, moveY);
    if (moveY < -900) {
      this.node.destroy();
    }
  }
}

运行查看效果

08a60c4410f6fb6d3b156479b0ab85c5.jpeg
动画

(3)设置敌机为预制件,拖入到/assets/prefab

(4)添加一个/assets/script/EnemyManager敌机管理器脚本,并添加接收预制件参数,把敌机拖入其中

a916348a86ea116a8c46f3da47116674.png

(5)用定时器随机位置不断创建敌机

与创建子弹一致,实例化预制件,可以在x轴上拖动敌机查看大概位置并设置敌机的移动范围,使用随机函数Math.random() * 400 + 40获取40-440的随机值赋值给x轴,并直接在敌机管理器中添加敌机。

import { _decorator, Component, instantiate, Node, Prefab } from "cc";
const { ccclass, property } = _decorator;

@ccclass("EnemyManager")
export class EnemyManager extends Component {
  @property(Prefab)
  enemy: Prefab;

  start() {
    const { x, y } = this.node.getPosition();
    this.schedule(() => {
      const node = instantiate(this.enemy);
      node.setPosition(Math.random() * 400 + 40, y);
      this.node.addChild(node);
    }, 0.5);
  }

  update(deltaTime: number) {}
}

查看效果

c80ad4244291ee7a7f57b2ea2b3fdc85.jpeg

碰撞检测

2D刚体组件文档:https://docs.cocos.com/creator/manual/zh/physics-2d/physics-2d-rigid-body.html

首先需要了解一下cocos中的物理引擎相关内容,为了让子弹和敌机碰撞,需要给他们添加物理引擎中的「2D刚体组件」「2D刚体」可以让游戏中的对象具有真实世界中的物体特性。例如,当你在游戏中扔一个球,2D 刚体可以让这个球在空中受到重力的影响,下落时与地面发生碰撞,并在碰撞后产生弹跳效果。这些都是基于物理学原理的模拟,让游戏中的对象表现得更加真实。

Cocos Creator 3.x中默认的物理引擎基于Box2d的2d物理系统,只有添加了刚体组件的物体才能进行碰撞检测。这是因为物理引擎是通过刚体之间的碰撞器组件进行碰撞检测的。

刚体组件(RigidBody)碰撞器组件(Collider)共同决定了物体的碰撞行为。刚体组件为物体提供物理属性,如质量、速度等,而碰撞器组件则定义了物体的形状和碰撞区域。当两个带有刚体组件和碰撞器组件的物体接触时,物理引擎会检测到碰撞并触发相应的碰撞事件。

(1)了解刚体组件(RigidBody2D)组件相关配置

在2D刚体中,有很多配置项,官网文档中讲的很详细,这里主要列举一下需要修改到的下面两个参数

属性说明
「EnabledContactListener」开启监听碰撞回调
「Type」刚体类型,详情请参考下方 「刚体类型」
刚体类型说明
「Static」静态刚体,零质量,零速度,即不会受到重力或速度影响,但是可以设置他的位置来进行移动。该类型通常用于制作场景
「Dynamic」动态刚体,有质量,可以设置速度,会受到重力影响。唯一可以通过 applyForceapplyTorque 等方法改版受力的刚体类型
「Kinematic」运动刚体,零质量,可以设置速度,不会受到重力的影响,但是可以设置速度来进行移动
「Animated」动画刚体,在上面已经提到过,从 Kinematic 衍生的类型,主要用于刚体与动画编辑结合使用

(2)给敌机和子弹添加刚体组件(RigidBody2D)盒碰撞组件(BoxCollider2D)

分别给双击敌机和子弹的预制件(Prefab),在右侧属性检查器中点击Add Component添加刚体组件(RigidBody2D)盒碰撞组件(BoxCollider2D)

72ff56cae988c2d2a141402b79a74518.png
image-20230925105208049

(3)配置刚体组件勾选EnabledContactListener和设置Type为Kinematic,并把敌机的BoxCollider2DTag的值修改为1,后续需要通过Tag进行判断是否是敌机,做一个标识。

943f1b9c0ee84953f7b7151e1649c54d.png
image-20230925143822260

(4)添加碰撞体回调函数

碰撞体回调文档:https://docs.cocos.com/creator/manual/zh/physics-2d/physics-2d-contact-callback.html

有两种实现方案可以触发碰撞体回调,「指定 collider 注册」「通过 2D 物理系统注册全局回调函数」

「指定 collider 注册」这种方式是将回调函数直接绑定到特定的 collider 组件上。这意味着,只有与该 collider 发生碰撞的事件才会触发这个回调函数。这样可以更精确地控制哪些对象需要处理碰撞事件,从而提高代码的可读性和可维护性。

这种方法的缺点是,如果您有多个对象需要处理碰撞事件,需要为每个对象分别注册回调函数。这可能会导致代码重复和更难管理。

「通过 2D 物理系统注册全局回调函数」这种方式是将回调函数注册到整个 2D 物理系统。这意味着,无论哪两个碰撞体发生碰撞,都会触发这个回调函数。这可以让您集中处理所有的碰撞事件,减少代码重复。

这种方法的缺点是,需要在回调函数中检查发生碰撞的对象,并根据需要执行相应的操作。这可能会导致回调函数变得庞大和复杂,从而降低代码的可读性和可维护性。

两种方法各有优缺点,可以根据项目的需求和个人编程风格来选择合适的方法

这次我选择使用全局回调方式在BgControl背景脚本上注册回调

通过 2D 物理系统在注册一个全局的回调函数,执行验证一下是否能够触发碰撞回调

start() {
     PhysicsSystem2D.instance.on(
       Contact2DType.BEGIN_CONTACT,
       this.onBeginContact,
       this
     );
   }
   // 只在两个碰撞体开始接触时被调用一次
   onBeginContact(self: Collider2D, other: Collider2D) {
     console.log("self",self,"other",other)
   }
03b367de46f3216208565af6caedba3a.png
image-20230925145240345

敌机销毁

当子弹碰撞到敌机后,敌机需要进行销毁,在敌机EnemyControl和子弹BulletControl脚本中添加销毁函数。

调用die方法后,执行销毁方法,并且通过isDead参数过滤掉多次的执行和死亡后的移动。

在代码中延迟销毁主要原因是因为

  1. 如果在销毁对象之前还有其他操作需要处理,立即销毁对象可能会导致这些操作无法正常执行。

  2. 如果销毁对象时,其他对象还在处理与该对象相关的操作,立即销毁可能会导致错误或异常。

  3. 后面敌机需要添加死亡动画

import { _decorator, Component, Node } from "cc";
    const { ccclass, property } = _decorator;
    
    @ccclass("EnemyControl")
    export class EnemyControl extends Component {
      isDead: boolean = false;
      start() {}
    
      update(deltaTime: number) {
        if (this.isDead) return;
        const { x, y } = this.node.getPosition();
        const moveY = y - 500 * deltaTime;
        this.node.setPosition(x, moveY);
        if (moveY < -900) {
          this.node.destroy();
        }
      }
    
      // 敌机死亡调用
      die() {
        if (this.isDead) return;
        this.isDead = true;
        setTimeout(() => {
          this.node?.destroy?.();
        }, 200);
      }
    }

子弹的销毁

子弹销毁和敌机销毁一致

import { _decorator, Component } from "cc";
    const { ccclass, property } = _decorator;
    
    @ccclass("BulletControl")
    export class BulletControl extends Component {
      isDead: boolean = false;
      start() {}
    
      update(deltaTime: number) {
        if (this.isDead) return;
        const { x, y } = this.node.getPosition();
        const moveY = y + 500 * deltaTime;
        this.node.setPosition(x, moveY);
        if (moveY > 800) {
          this.node.destroy();
        }
      }
    
      
      die() {
        if (this.isDead) return;
        this.isDead = true;
        setTimeout(() => {
          this.node?.destroy?.();
        }, 10);
      }
    }

碰撞执行销毁

碰撞回调出发后,判断子弹和敌机碰撞,执行销毁两个的对象。

修改BgControl回调方法, 当tag等于1和0的时候,说明是子弹和敌机相撞,销毁对应的对象。

// 只在两个碰撞体开始接触时被调用一次
      onBeginContact(self: Collider2D, other: Collider2D) {
        if (other.tag === 1 && self.tag === 0) {
          other.getComponent(EnemyControl).die();
          self.getComponent(BulletControl).die();
        } else if (other.tag === 0 && self.tag === 1) {
          other.getComponent(BulletControl).die();
          self.getComponent(EnemyControl).die();
        }
      }

查看效果

d19c56cca10c1fdffa0cb93c64ad57ef.gif
contact-over

添加死亡动画

加载资源文档:https://docs.cocos.com/creator/manual/zh/asset/dynamic-load-resources.html

简单实现就是通过加载多张图片,循环替换图片即可。

将敌机死亡的几张图片放入assets/resources/enemy-death

d6407ea04b11983ee95b0d95652adf29.png
image-20230925152757096

Cocos Creator 2.4 以前是通过 loader 模块加载,网上一些旧的资料都是使用的loader模块,与现在的新版本不兼容,在之后的版本使用assetManager下的resources进行操作。

编写敌机脚本EnemyControl添加加载图片方法,使用resources.loadDir加载整个文件夹资源,并且把加载的资源使用变量保存下来。

// 加载图片
      loadImages() {
        resources.loadDir(
          "enemy-death",
          SpriteFrame,
          (_err, spriteFrames) => {
            this.airplaneDeadImages = spriteFrames;
          }
        );
      }

添加播放动画,循环播放加载的资源,使用延时方式执行

// 播放死亡动画
      playDead() {
        for (let i = 0; i < this.airplaneDeadImages.length; i++) {
          setTimeout(() => {
            if (this.node.getComponent) {
              this.node.getComponent(Sprite).spriteFrame =
                this.airplaneDeadImages[i];
            }
          }, i * 80);
        }
      }

最后进行调用加载图片和播放方法,完整代码

import { _decorator, resources, Component, Sprite, SpriteFrame } from "cc";
    const { ccclass, property } = _decorator;
    
    @ccclass("EnemyControl")
    export class EnemyControl extends Component {
      isDead: boolean = false;
      airplaneDeadImages = [];
    
      start() {
        this.loadImages();
      }
    
      update(deltaTime: number) {
        if (this.isDead) return;
        const { x, y } = this.node.getPosition();
        const moveY = y - 500 * deltaTime;
        this.node.setPosition(x, moveY);
        if (moveY < -900) {
          this.node.destroy();
        }
      }
    
      // 加载图片
      loadImages() {
        resources.loadDir(
          "enemy-death",
          SpriteFrame,
          (_err, spriteFrames) => {
            this.airplaneDeadImages = spriteFrames;
          }
        );
      }
    
      // 播放死亡动画
      playDead() {
        for (let i = 0; i < this.airplaneDeadImages.length; i++) {
          setTimeout(() => {
            if (this.node.getComponent) {
              this.node.getComponent(Sprite).spriteFrame =
                this.airplaneDeadImages[i];
            }
          }, i * 80);
        }
      }
    
      // 敌机死亡调用
      die() {
        if (this.isDead) return;
        this.isDead = true;
        this.playDead();
        setTimeout(() => {
          this.node?.destroy?.();
        }, 300);
      }
    }

最终效果

d00a080691e070b3f28b465d1d22a2fa.jpeg

打包构建

打包构建就比较简单了,右上角点击 Build,选择Web Mobile后点击Build就开始构建啦。

总结

完整代码:https://github.com/yiqia/cocos-plane

通过这次实践,了解了 Cocos Creator 3.x 的基本概念和使用方法,并成功实现了一个简单的飞机大作战游戏。在这个过程中,实现创建场景、节点、预制件以及编写脚本来控制游戏对象的行为。同时,使用物理引擎中的刚体组件和碰撞器组件来实现碰撞检测,以及如何加载资源和简单版播放动画。

虽然这个游戏还有很多可以优化和完善的地方,还可以继续完善游戏,例如添加新的场景,添加开始游戏和结束游戏,以及生命值,关卡等扩展,但通过这个实践,已经掌握了 Cocos Creator 3.x 的基本使用方法,为以后开发更复杂的游戏奠定了基础,可以尝试更多关于 Cocos Creator 的知识,如动画系统、粒子系统、UI 系统等,以便能够制作更加丰富和有趣的游戏。

如果文章对你有帮助的话欢迎

「关注+点赞+收藏」

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值