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

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

两天入门,cocos开发飞机大作战_第1张图片

效果与预览

两天入门,cocos开发飞机大作战_第2张图片

手机扫码体验:

两天入门,cocos开发飞机大作战_第3张图片 下载

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-x Cocos Creator
编程语言 C++, Lua, JavaScript JavaScript, TypeScript
平台支持 iOS, Android, Windows, macOS, Linux, HTML5 iOS, 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/

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

两天入门,cocos开发飞机大作战_第4张图片 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

两天入门,cocos开发飞机大作战_第5张图片 image-20230921174048323
调整分辨率

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

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

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

两天入门,cocos开发飞机大作战_第6张图片

背景循环移动

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

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

两天入门,cocos开发飞机大作战_第7张图片 image-20230922141624763
  1. 实现滚动脚本

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

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

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

两天入门,cocos开发飞机大作战_第8张图片 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 这个数值,那么元素的移动速度将会与帧率直接关联。在帧率较高的设备上,元素会移动得更快;而在帧率较低的设备上,元素会移动得更慢。这会导致游戏在不同设备上的表现和玩家体验不一致。

「运行查看效果」

p3ufm-5s27j

添加飞机

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

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

两天入门,cocos开发飞机大作战_第9张图片

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) {}
}

「运行查看效果」

9rb9j-ej7fh

添加子弹

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

两天入门,cocos开发飞机大作战_第10张图片
  1. 编写子弹内容,设置子弹位置不断往前,很简单的代码,获取坐标,并且y坐标增加。

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

「预览效果」

两天入门,cocos开发飞机大作战_第11张图片 7epbv-54mwo
  1. 添加「预制件(Prefab)」

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

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

两天入门,cocos开发飞机大作战_第12张图片 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) {}
}

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

两天入门,cocos开发飞机大作战_第13张图片 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敌机拖入空节点中,移动空节点到顶部屏幕外

两天入门,cocos开发飞机大作战_第14张图片

(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();
    }
  }
}

运行查看效果

两天入门,cocos开发飞机大作战_第15张图片 动画

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

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

两天入门,cocos开发飞机大作战_第16张图片

(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) {}
}

查看效果

两天入门,cocos开发飞机大作战_第17张图片

碰撞检测

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)

两天入门,cocos开发飞机大作战_第18张图片 image-20230925105208049

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

两天入门,cocos开发飞机大作战_第19张图片 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)
   }
两天入门,cocos开发飞机大作战_第20张图片 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();
        }
      }

查看效果

两天入门,cocos开发飞机大作战_第21张图片 contact-over

添加死亡动画

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

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

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

两天入门,cocos开发飞机大作战_第22张图片 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);
      }
    }

最终效果

两天入门,cocos开发飞机大作战_第23张图片

打包构建

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

总结

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

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

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

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

「关注+点赞+收藏」

你可能感兴趣的:(两天入门,cocos开发飞机大作战)