每个人的梦想都不一样,有大有小,有难有易,但想要实现梦想,有一个共同点,那就是得要迈出第一步,“开始”追逐梦想。只要我们“开始”了,不论是好的开始,还是不好的开始,那都是开始,都算是迈出了第一步,也就意味着,在实现梦想的大道上跨出了一大步。
引言:追逐梦想的第一步
时间的流逝让我意识到,曾经认为可以无限期推迟的事情,现在一件也没做成,这让我感到迷茫和沮丧,后来看了一本励志书籍启发了我,让我决定必须采取行动,选一个自己最想做的事从头开始,开发一款真正属于我自己的传奇游戏。
本教程是学习笔记,通过简单的飞机大战将能遇到的功能点都实现一遍,然后开始搞我的传奇。
引擎的选择
国内外的游戏引擎众多,大同小异具体选择什么还是看开发者的喜好,有人喜欢C#,有人喜欢C,有人喜欢Lua,还有人喜欢TS,根据自己的编程语言能力和喜好选择一个平台就好,其实每平台各有千秋,原理相同叫法各异,所以会了一个其实大部分也就通了,所以初学者无需纠结,选择一个有眼缘的就好,我选择Cocos Creater 主要看重的是国产平台,至少不用掐脖子,并且它的支持度相对较高,尽管我认为学习成本可能比 Unity3D 要高一些。
准备
Cocos Creator 3.8.3:本文将展示如何使用这个版本的 Cocos Creator。有关编译器的使用方法,可以参考官方视频和文档。
Visual Studio Code:脚本语言使用 Type Script
TexturePackerGUI:图片整合工具 (如何白嫖请自行查找,实在不会可以私信)
素材:背景图、己方飞机图、敌方飞机图、发射音效、飞机爆炸音效等
效果图
新建PlaneWar项目
安装Cocos Dashboard,创建一个空2D项目。
我喜欢将属性检查器平移到左侧,方便资源的拖拽存是个人喜好。
添加必要资源和项目属性设定
- 将需要用的图片声音等等拖拽到资源管理器中,不明白的可以查看官方文档不在此赘述;
- 根据拖入的背景图片的大小调整项目的分辨率。
选择项目>项目设置
根据图片的大小和方向,对应项目数据中的 宽、高及屏幕适配。
实现循环背景
效果图中,天空背景从上往下移动,如果放上飞机看上去就好像飞机在空中飞行一样。想要实现
背景滚动的效果,无外乎就是一组图片连续的滚动播放,第一个图片滚到到终点后移动至队列的末尾,以此类推,实现图片的滚动效果,也就是飞机在空中飞行的视觉效果。
创建背景空节点及背景精灵
- 创建一空节点用于存放背景相关的图片精灵 SkyNode;
- 创建两个Sprite精灵 SkySprite/SkySprite-1,将背景图片分别拖拽到精灵的Sprite Frame中;
创建脚本实现背景滚动
创建脚本
资源管理器 左上角的 + 按钮,点击后选择 TypeScript > NewComponent,修改名称为SkyScript,
注意:
项目中所有脚本的类名不允许重复,即使脚本文件在不同的目录下,各自的代码里也不允许有相同的类名。
脚本挂载到场景的节点中
选择想要挂载脚本的节点,点击添加组件>自定义脚本>选择创建好的SkyScript
或者也可以通过拖拽的方式,将脚本拖入节点属性检查器中。
属性装饰器
属性装饰器@property简写@type,用于控制 Cocos Creator 编辑器中对该属性的序列化、属性检查器 中对该属性的展示使用,具体属性使用方法移步官方文档查看。
本章只用到了Node类型,Node是用于存放界面的节点,对象或者组件等,用于实现代码动态编辑和调整。
@type([Node])
skies: Node[] = [];
创建 skies数组用于存放,背景图片精灵
通过@property或@type设定的属性,可以在前端面板上进行修改,将两个背景精灵分别拖拽到数组中。
生命周期
Cocos 生命周期:
- onLoad
- onEnable
- start
- update
- lateUpdate
- onDisable
- onDestroy
当一个脚本添加到一个场景中是激活的状态,就会主动执行初始化事件函数 onLoad,onEnable start,其中onLoad和start只会在组件的生命周期内有且仅执行一次,在生命周期内每帧都会执行一次update和lateUpdate,当组件被或节点被禁用时会调用onDisable,再次激活的时候会执行onEnable函数。销毁之前就会调用onDestroy。
初始化数据设置背景图的位子
背景图的位子有两种方式:
第一种是直接在场景编辑器中进行拖拽或者是修改位子数值,如果背景图很多很长不建议手动界面修改;
第二种是通过编码方式将对场景中的各个图片节点的位子进行计算赋值;
- 获取背景图片的高度:通过skies节点数组,取出第一张背景节点,通过
getComponent
获取节点上UITransform
组件来获取图片的高度, - 对节点数组进行遍历,从第二个节点开始所有节点的位子按照当前节点与高度的倍数进行移动排列。
onLoad() {
this._skyHeight = this.skies[0].getComponent(UITransform).height;
for (let i = 1; i <= this.skies.length; i++) {
this.skies[i].setPosition(this.skies[i].position.x, this._skyHeight * i);
}
}
背景动起来
如何能让图片组滚动起来,在update中遍历节点数组,拿到节点坐标值,将y轴坐标减去一个固定的值,当y坐标值小于等于负数的图片高度的时候,图片已经滚出了屏幕,将滚出屏幕的图片重置到队列的尾部。
update(deltaTime: number) {
for (let i = 0; i < this.skies.length; i++) {
if (this.skies[i].position.y <= this._skyHeight * -1) {
this.skies[i].setPosition(
this.skies[i].position.x,
this.skies[i + 1 < this.skies.length ? i + 1 : 0].position.y +
this._skyHeight
);
}
this.skies[i].setPosition(
this.skies[i].position.x,
this.skies[i].position.y - 300 * deltaTime
);
}
}
注意:deltaTime
不同手机的型号、分辨率和性能等原因,导致每一帧的播放时间可能会有所不同,为了解决这个问题,引入了deltaTime。deltaTime是一个表示两帧之间经过的时间(以秒为单位)的值,通过使用deltaTime可以使游戏中的元素运动更加平滑,所以y值减去的不是300,是300*deltaTime,使得不同的机型在运行同一场景时保持一致。
完成的代码
import { _decorator, Component, Node, UITransform, Vec3 } from "cc";
const { ccclass, property, type } = _decorator;
@ccclass("SkyScript")
export class SkyScript extends Component {
/// 这三种方式都可以
/// @type([Node])
/// @property([Node])
/// @property({type:[Node]})
@type([Node])
skies: Node[] = [];
_skyHeight: number = 0;
onLoad() {
this._skyHeight = this.skies[0].getComponent(UITransform).height;
for (let i = 1; i <= this.skies.length; i++) {
this.skies[i].setPosition(this.skies[i].position.x, this._skyHeight * i);
}
}
update(deltaTime: number) {
for (let i = 0; i < this.skies.length; i++) {
if (this.skies[i].position.y <= this._skyHeight * -1) {
this.skies[i].setPosition(
this.skies[i].position.x,
this.skies[i + 1 < this.skies.length ? i + 1 : 0].position.y +
this._skyHeight
);
}
this.skies[i].setPosition(
this.skies[i].position.x,
this.skies[i].position.y - 300 * deltaTime
);
}
}
}
我的公众号