纯小白用AI开发简易版雷电大战

0 阅读21分钟

雷霆战机开发回顾

1. 项目目标

这次我们使用 Cocos Creator 3.8.8,从零开始搭建了一款单机版雷霆战机的基础版本。

当前已经完成的核心内容:

  • 创建 2D + TypeScript 项目
  • Main.scene 中创建玩家飞机
  • 实现玩家拖动移动
  • 实现自动发射子弹
  • 实现敌机定时生成
  • 实现子弹与敌机碰撞
  • 实现分数系统
  • 实现玩家血量系统
  • 实现游戏结束界面
  • 实现重新开始游戏

这说明项目已经从“空场景”进入到了“可玩原型”阶段。


2. 我们做了什么

2.1 创建项目

一开始我们在 Cocos Creator 中创建了一个 2D 项目,并选择了 TypeScript 作为脚本语言。

这样做的原因:

  • 2D 更适合做雷霆战机这类竖版飞行射击游戏
  • TypeScript 比纯 JavaScript 更规范,后面项目变大时更容易维护

2.2 创建玩家飞机

我们在场景文件 Main.scene 中创建了 Player 节点,并把它放到 Canvas 下面。

要点:

  • 节点是在场景里创建,不是在脚本文件里创建
  • Player 要放在 Canvas 下,方便显示在 UI 场景中
  • Player 添加 Sprite 组件,让飞机图片显示出来

这里学到的概念:

  • Hierarchy:场景里有哪些节点
  • Assets:项目里有哪些资源
  • Inspector:当前节点的属性和组件
  • Canvas:2D 游戏和 UI 常用的根节点

2.3 让飞机可以移动

我们创建了脚本 PlayerController.ts,并挂到 Player 节点上。

这个脚本完成了:

  • 监听鼠标或手指拖动
  • 根据触摸位置移动飞机
  • 限制飞机不能飞出屏幕

这里学到的概念:

  • 脚本需要挂到节点上才会生效
  • update() 用于每帧执行逻辑
  • 可以通过 view.getVisibleSize() 获取屏幕可视区域

2.4 实现自动发射子弹

我们先创建了 Bullet 节点,再做成 Bullet.prefab 预制体。

然后创建了两个脚本:

  • Bullet.ts
  • PlayerShoot.ts

各自负责:

  • Bullet.ts:控制子弹向上飞行,飞出屏幕后销毁
  • PlayerShoot.ts:控制玩家按固定时间间隔自动发射子弹

这里学到的概念:

  • Prefab 是可以重复生成的模板
  • 运行时实例化预制体要用 instantiate
  • 子弹这类对象通常是动态创建的,不是手动摆满场景

2.5 实现敌机生成

我们创建了 Enemy 节点和 Enemy.prefab,然后又创建了:

  • Enemy.ts
  • EnemySpawner.ts

功能分别是:

  • Enemy.ts:控制敌机向下飞,飞出屏幕后销毁
  • EnemySpawner.ts:定时随机生成敌机

这里学到的概念:

  • 生成器节点可以专门负责“刷怪”
  • 敌机的出生位置可以随机化
  • 游戏里的对象逻辑最好拆分,不要全写在一个脚本里

2.6 实现碰撞检测

我们给 BulletEnemyPlayer 都加上了碰撞和刚体组件:

  • BoxCollider2D
  • RigidBody2D

同时还学习了几个关键设置:

  • Type = Kinematic
  • Enabled Contact Listener 要勾上
  • Gravity Scale = 0

然后通过代码监听碰撞事件,实现:

  • 子弹碰到敌机,双方销毁
  • 敌机碰到玩家,玩家掉血

这里学到的概念:

  • 有碰撞组件不代表一定能收到碰撞回调
  • RigidBody2D 的接触监听必须正确开启
  • 调试碰撞时,要先确认组件、名字、脚本、监听都对

2.7 实现分数和血量

我们创建了 GameManager 节点和 GameManager.ts 脚本,用它统一管理游戏状态。

它负责:

  • 记录分数
  • 记录玩家血量
  • 更新界面文字
  • 判断游戏结束

同时在 Canvas 下创建了两个文本:

  • ScoreLabel
  • HpLabel

这里学到的概念:

  • GameManager 适合做全局管理
  • 分数和血量这类数据最好集中管理
  • UI 文字通常通过 Label 组件更新

2.8 实现游戏结束和重新开始

我们又做了 GameOverPanel,并在里面加入:

  • Game Over 文字
  • RestartButton 按钮

然后在 GameManager.ts 中加入:

  • 游戏结束时显示面板
  • 点击按钮后重新加载 Main.scene

这里学到的概念:

  • Button 组件本身不显示文字
  • 按钮文字通常由它的 Label 子节点负责
  • director.loadScene('Main') 可以重新载入场景

3. 目前项目里的核心脚本

你现在的项目,核心脚本大致可以这样理解:

PlayerController.ts

负责玩家移动。

PlayerShoot.ts

负责玩家自动发射子弹。

Bullet.ts

负责子弹移动和子弹碰撞逻辑。

Enemy.ts

负责敌机移动和敌机碰撞逻辑。

EnemySpawner.ts

负责定时生成敌机。

GameManager.ts

负责分数、血量、游戏结束、重新开始等全局逻辑。


4. 目前项目里的核心预制体

Bullet.prefab

子弹模板,发射时动态生成。

Enemy.prefab

敌机模板,刷怪时动态生成。

Explosion.prefab

爆炸模板,目前还在调试阶段。


5. 这次开发中最重要的学习点

5.1 节点和脚本是分开的

节点是场景中的对象,脚本是给对象添加行为。

简单理解:

  • 节点 = 游戏里的角色或物体
  • 脚本 = 这些物体怎么动、怎么打、怎么碰撞

5.2 预制体非常重要

像子弹、敌机、爆炸这种会反复出现的对象,不适合每次手动创建,所以要做成 Prefab


5.3 游戏逻辑要拆分

不要把所有代码写进一个文件。

我们现在的拆分方式已经是比较合理的入门结构:

  • 玩家逻辑一个脚本
  • 子弹逻辑一个脚本
  • 敌机逻辑一个脚本
  • 刷怪逻辑一个脚本
  • 全局管理一个脚本

5.4 先做功能,再做美术

这次开发中,我们一直优先让“功能可运行”,而不是先追求画面精致。

这是一个非常正确的开发顺序:

  1. 先能运行
  2. 再能玩
  3. 再好看
  4. 再精细优化

6. 你已经掌握的开发流程

通过这次练习,你已经实际走过了一遍最基础的游戏开发流程:

  1. 创建项目
  2. 创建场景
  3. 创建节点
  4. 添加图片显示
  5. 创建脚本
  6. 挂载脚本
  7. 创建预制体
  8. 动态生成对象
  9. 添加碰撞检测
  10. 制作基础 UI
  11. 管理游戏状态

这套流程以后做别的 2D 游戏也一样能用。


7. 目前还有哪些可以继续完善

你现在的项目已经是一个“能玩的原型”,但距离更完整的雷霆战机还可以继续做这些内容:

  • 爆炸动画完善
  • 双发子弹
  • 火力升级
  • 敌机血量系统
  • Boss 战
  • 道具掉落
  • 音效和背景音乐
  • 开始界面
  • 暂停界面
  • 关卡系统
  • 不同敌机类型
  • 玩家无敌时间
  • 对象池优化

8. 下一阶段建议学习顺序

如果你接下来继续做,我建议按这个顺序推进:

  1. 先把爆炸效果调通
  2. 再做双发子弹
  3. 再做敌机血量和不同敌机
  4. 再做掉落道具和火力升级
  5. 最后再做 Boss、关卡、音效和美术优化

这样难度是逐步上升的,不容易乱。


9. 每个脚本逐段解释版

这一部分不是死抠语法,而是帮助你理解“每个脚本为什么要这样写”。

9.1 PlayerController.ts

这个脚本的目标是让玩家飞机跟着鼠标或手指移动。

常见结构可以理解成下面几段:

导入模块
import { _decorator, Component, EventTouch, input, Input, UITransform, Vec3, view } from 'cc';

这里是在从 cc 里取出后面会用到的工具。

  • _decorator:用来写 @ccclass
  • Component:所有脚本组件的基础类
  • EventTouch:触摸事件对象
  • inputInput:监听输入事件
  • UITransform:读取节点宽高
  • Vec3:表示三维坐标
  • view:获取屏幕可视区域
定义类
@ccclass('PlayerController')
export class PlayerController extends Component

这表示我们定义了一个叫 PlayerController 的组件脚本,它可以挂到节点上。

定义成员变量
private halfPlayerWidth = 0;
private halfPlayerHeight = 0;
private halfScreenWidth = 0;
private halfScreenHeight = 0;

这些变量用来记录:

  • 飞机自身的一半宽高
  • 屏幕可视区域的一半宽高

为什么要记录一半:

  • 因为限制边界时,不能只看节点中心点
  • 还要考虑飞机图片本身的大小
start()

start() 会在脚本启用后执行一次。

在这里主要做了两件事:

  1. 读取飞机尺寸
  2. 注册触摸移动事件

大致思路是:

const uiTransform = this.getComponent(UITransform);

这句是从当前节点上拿到 UITransform 组件,从而得到节点宽高。

const visibleSize = view.getVisibleSize();

这句是获取屏幕可视区域大小。

input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);

这句的意思是:

  • 监听拖动事件
  • 当拖动发生时
  • 调用 onTouchMove
onDestroy()
input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);

这句用于取消事件监听。

为什么要取消:

  • 防止节点销毁后还保留旧监听
  • 避免重复监听和潜在 bug
onTouchMove(event)

这是最核心的方法,负责真正移动飞机。

const uiLocation = event.getUILocation();

获取当前鼠标或手指在 UI 坐标系里的位置。

let x = uiLocation.x - this.halfScreenWidth;
let y = uiLocation.y - this.halfScreenHeight;

这一步是在做坐标换算。

原因是:

  • 触摸位置通常是以屏幕左下角为原点
  • 场景里的节点位置更常以中心为参考

所以要减去半个屏幕宽高。

x = Math.max(...Math.min(...));
y = Math.max(...Math.min(...));

这两句是限制边界,防止飞机飞出屏幕。

this.node.setPosition(new Vec3(x, y, 0));

最后把飞机移动到目标位置。

这个脚本的核心理解

一句话概括:

监听拖动输入,换算坐标,并把玩家节点移动到合法范围内。


9.2 PlayerShoot.ts

这个脚本的目标是让飞机自动按固定节奏发射子弹。

预制体属性
@property(Prefab)
public bulletPrefab: Prefab | null = null;

这表示:

  • 脚本里有一个可在编辑器中拖拽赋值的子弹预制体
  • 运行时发射的子弹,就是根据这个预制体复制出来的
发射间隔
@property
public shootInterval = 0.2;

表示每隔 0.2 秒发射一次。

计时器
private shootTimer = 0;

用来累计经过的时间。

update(deltaTime)
this.shootTimer += deltaTime;

deltaTime 是这一帧距离上一帧经过的秒数。

把它不断累加,就能知道“已经过了多久”。

if (this.shootTimer >= this.shootInterval)

如果累计时间超过设定间隔,就发射一次。

this.shootTimer = 0;
this.shoot();

发射后把计时器清零,重新开始下一轮计时。

shoot()
const bullet = instantiate(this.bulletPrefab);

复制一个新的子弹节点。

this.node.parent.addChild(bullet);

把子弹加入当前场景。

这里用 this.node.parent,因为玩家节点和子弹节点通常都放在同一个父节点下,比如 Canvas

const playerPos = this.node.position;
bullet.setPosition(new Vec3(playerPos.x, playerPos.y + 80, 0));

这句表示把子弹放到玩家前方一点的位置,这样视觉上就像从飞机头部发射出去。

这个脚本的核心理解

一句话概括:

用一个计时器,每隔固定时间复制出一个子弹预制体。


9.3 Bullet.ts

这个脚本控制子弹移动和命中敌机后的处理。

速度属性
@property
speed: number = 1000;

表示子弹向上飞行的速度。

监听碰撞
const collider = this.getComponent(Collider2D);
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);

意思是:

  • 先拿到当前子弹的碰撞器
  • 再注册“开始接触”事件

当子弹碰到其他物体时,就会调用 onBeginContact

update(deltaTime)
const pos = this.node.position;
this.node.setPosition(new Vec3(pos.x, pos.y + this.speed * deltaTime, pos.z));

这表示每一帧都让子弹沿着 y 轴正方向移动。

为什么要乘 deltaTime

  • 这样移动速度就和帧率无关
  • 不管电脑快还是慢,子弹每秒移动距离都差不多
if (this.node.position.y > 1200) {
    this.node.destroy();
}

子弹飞出屏幕后就删除,避免场景里积累太多无用节点。

onBeginContact(...)

通常逻辑是:

if (otherCollider.node.name === 'Enemy') {
    GameManager.instance?.addScore(10);
    GameManager.instance?.createExplosion(otherCollider.node.position.clone());
    otherCollider.node.destroy();
    this.node.destroy();
}

这段代码含义是:

  • 如果撞到的是敌机
  • 先加分
  • 再创建爆炸
  • 再销毁敌机
  • 最后销毁子弹
这个脚本的核心理解

一句话概括:

子弹每帧向上飞,碰到敌机后触发命中处理。


9.4 Enemy.ts

这个脚本负责敌机下落以及撞到玩家后的处理。

敌机速度
@property
speed: number = 300;

表示敌机向下飞行的速度。

update(deltaTime)
this.node.setPosition(new Vec3(pos.x, pos.y - this.speed * deltaTime, pos.z));

因为敌机是往下飞,所以这里是 y - speed * deltaTime

飞出屏幕后销毁
if (this.node.position.y < -1200) {
    this.node.destroy();
}

避免敌机飞出屏幕后还一直留在场景里。

碰到玩家

敌机的碰撞逻辑通常写在 onBeginContact() 中。

if (otherCollider.node.name === 'Player') {
    GameManager.instance?.takeDamage(1);
    this.node.destroy();
}

意思是:

  • 如果碰到的是玩家
  • 玩家扣 1 点血
  • 当前敌机销毁
这个脚本的核心理解

一句话概括:

敌机持续向下移动,撞到玩家就让玩家掉血。


9.5 EnemySpawner.ts

这个脚本控制敌机按固定节奏生成。

敌机预制体属性
@property(Prefab)
public enemyPrefab: Prefab | null = null;

这是在编辑器中指定“要生成哪种敌机”。

生成间隔
@property
public spawnInterval: number = 1.0;

表示每 1 秒生成一次敌机。

start()

在开始时记录屏幕宽高,用于后面计算出生范围。

const visibleSize = view.getVisibleSize();
this.halfScreenWidth = visibleSize.width * 0.5;
this.halfScreenHeight = visibleSize.height * 0.5;
update(deltaTime)

PlayerShoot.ts 的思路很像,也是通过计时器累计时间,时间到了就生成一架敌机。

spawnEnemy()

这是真正创建敌机的方法。

const enemy = instantiate(this.enemyPrefab);
this.node.parent.addChild(enemy);

这两句表示:

  • 复制一架敌机
  • 把它加到场景里
const minX = -this.halfScreenWidth + 50;
const maxX = this.halfScreenWidth - 50;
const randomX = minX + Math.random() * (maxX - minX);

这几句是生成一个随机横坐标,让敌机不会每次都从同一位置出现。

const spawnY = this.halfScreenHeight + 100;
enemy.setPosition(new Vec3(randomX, spawnY, 0));

把敌机放在屏幕顶部之外一点的位置,看起来就像从上面飞进来。

这个脚本的核心理解

一句话概括:

每隔一段时间,在屏幕上方随机位置生成一架敌机。


9.6 GameManager.ts

这是整个项目中最接近“大脑”的脚本。

它主要负责:

  • 记录分数
  • 记录血量
  • 更新 UI
  • 游戏结束
  • 重新开始
  • 生成爆炸
静态实例
public static instance: GameManager;

这是为了让别的脚本能方便访问它。

比如:

GameManager.instance?.addScore(10);
UI 属性
@property(Label)
public scoreLabel: Label | null = null;

@property(Label)
public hpLabel: Label | null = null;

这两个属性是让你在编辑器中把界面上的文字节点拖进来。

面板属性
@property(Node)
public gameOverPanel: Node | null = null;

这是为了在游戏结束时显示失败界面。

数据变量
public playerHp: number = 3;
private score: number = 0;
private isGameOver: boolean = false;

分别表示:

  • 玩家初始生命值
  • 当前分数
  • 当前是否已经结束
onLoad()
GameManager.instance = this;

把当前脚本实例保存到静态变量上,让别的脚本可以访问到。

start()

这里通常做初始化工作:

  • 隐藏游戏结束界面
  • 初始化分数显示
  • 初始化血量显示
addScore(value)

表示增加分数。

典型流程:

  1. 如果游戏已经结束,直接返回
  2. 分数增加
  3. 更新分数字样
takeDamage(value)

表示玩家受伤。

典型流程:

  1. 如果已经结束,直接返回
  2. 扣减血量
  3. 限制最低不能小于 0
  4. 更新血量显示
  5. 如果血量为 0,触发游戏结束
restartGame()
director.resume();
director.loadScene('Main');

这表示:

  • 先恢复被暂停的游戏
  • 再重新加载主场景
gameOver()

通常负责:

  • 标记已结束
  • 显示 GameOverPanel
  • 暂停游戏
createExplosion(position)

这个方法是为了把爆炸逻辑统一交给 GameManager 管理。

它的常见流程:

  1. 判断 Explosion.prefab 是否存在
  2. 实例化爆炸节点
  3. 把爆炸节点加入场景
  4. 设置爆炸出现的位置
这个脚本的核心理解

一句话概括:

统一管理全局数据、UI 状态和游戏流程。


9.7 Explosion.ts

这个脚本负责爆炸动画播放。

帧数组
@property([SpriteFrame])
public frames: SpriteFrame[] = [];

表示这个爆炸动画由多张图片组成。

帧间隔
@property
public frameInterval: number = 0.08;

表示每隔多久切换到下一张爆炸图。

start()

主要做两件事:

  1. 取到当前节点的 Sprite
  2. 把第一张图设置为初始显示图
update(deltaTime)

这里是动画播放的关键。

大致逻辑是:

  1. 累计时间
  2. 到达 frameInterval 后切换下一帧
  3. 如果帧已经播放完,就销毁节点
这个脚本的核心理解

一句话概括:

让爆炸节点按顺序播放多张图片,播放完后自动删除。


10. Cocos 常用面板和组件速查版

这一部分你可以当作入门速查表,以后忘了就回来查。

10.1 常用面板

Hierarchy

作用:

  • 查看场景中的节点结构
  • 创建节点
  • 调整父子关系

你这次最常做的事:

  • 创建 Player
  • 创建 GameManager
  • Player 拖到 Canvas
Assets

作用:

  • 管理项目资源
  • 存放图片、脚本、预制体、场景

你这次最常做的事:

  • 导入飞机图片
  • 导入敌机图片
  • 导入爆炸图片
  • 创建脚本
  • 查看 spriteFrame
Inspector

作用:

  • 修改选中节点的属性和组件
  • 给节点挂脚本
  • 调整坐标、缩放、图片、碰撞体

你这次最常做的事:

  • 给节点添加 Sprite
  • 给节点添加 Button
  • 给节点添加 BoxCollider2D
  • 给节点拖入预制体引用
Scene

作用:

  • 直接在场景中摆放节点
  • 移动、缩放、查看当前布局
Console

作用:

  • 查看报错
  • 输出调试信息

你这次最常做的事:

  • 看碰撞有没有报错
  • 检查脚本有没有执行

10.2 常用节点

Canvas

作用:

  • 2D 游戏常用根节点
  • UI 元素和大部分 2D 节点通常都挂在它下面
普通空节点

作用:

  • 用来作为逻辑节点
  • 本身不显示图片

适合做:

  • GameManager
  • EnemySpawner
Label

作用:

  • 显示文字

适合做:

  • 分数显示
  • 血量显示
  • Game Over
Button

作用:

  • 响应点击事件

要点:

  • Button 本身不负责显示文字
  • 一般要搭配子节点 Label

10.3 常用组件

cc.UITransform

作用:

  • 控制节点的宽高
  • 提供 UI 相关尺寸信息

你这次用它做了:

  • 读取玩家节点大小
  • 设置碰撞区域参考
cc.Sprite

作用:

  • 显示一张图片

常用属性:

  • Sprite Frame:当前显示哪张图
  • Size Mode:图片大小模式

适合做:

  • 玩家飞机
  • 子弹
  • 敌机
  • 爆炸图
cc.Button

作用:

  • 给节点添加点击能力

常用属性:

  • Target
  • Transition
  • Click Events
Label

作用:

  • 显示文字内容

常用属性:

  • String
  • Font Size
  • Line Height
BoxCollider2D

作用:

  • 定义 2D 碰撞范围

常用属性:

  • Size
  • Offset
  • Is Sensor
RigidBody2D

作用:

  • 参与 2D 物理和碰撞系统

这次你最常用的设置:

  • Type = Kinematic
  • Enabled Contact Listener = 勾选
  • Gravity Scale = 0
自定义脚本组件

作用:

  • 给节点添加自定义逻辑

例如:

  • PlayerController
  • PlayerShoot
  • Bullet
  • Enemy
  • EnemySpawner
  • GameManager
  • Explosion

10.4 常用资源类型

Scene

作用:

  • 保存一个完整场景

例如:

  • Main.scene
Prefab

作用:

  • 保存一个可重复生成的节点模板

例如:

  • Bullet.prefab
  • Enemy.prefab
  • Explosion.prefab
SpriteFrame

作用:

  • 图片在 Sprite 中真正使用的资源

要点:

  • 拖到 Sprite Frame 里的通常是它
  • 不是原始 texture

10.5 这次开发中最常见的报错来源

以后如果再遇到问题,优先检查这几类:

  • 脚本没有挂到正确节点上
  • 改的是场景节点,不是预制体节点
  • 预制体引用没有拖进去
  • 节点名字和代码判断不一致
  • RigidBody2D 没开 Enabled Contact Listener
  • 资源拖进去时用了 texture,没有用 spriteFrame
  • 节点没有放在 Canvas 下,导致显示异常

11. 项目目录结构说明版

这一部分的目标是帮助你看懂“项目文件为什么这样放”,以后你自己加功能时,就知道该把资源放到哪里。

11.1 当前项目可以参考的目录结构

assets/
  scenes/
    Main.scene
  scripts/
    PlayerController.ts
    PlayerShoot.ts
    Bullet.ts
    Enemy.ts
    EnemySpawner.ts
    GameManager.ts
    Explosion.ts
  prefabs/
    Bullet.prefab
    Enemy.prefab
    Explosion.prefab
  textures/
    TF.png
    enemy.png
    explosion_1.png
    explosion_2.png
    explosion_3.png
    explosion_4.png

这不是唯一正确答案,但对你现在这个阶段来说,是一个非常清晰、非常适合继续扩展的结构。


11.2 assets/ 是最重要的资源目录

assets/ 可以理解成游戏项目最核心的资源区。

你在编辑器里看到的大多数内容,最终都在这里。

例如:

  • 场景文件
  • 脚本文件
  • 图片资源
  • 预制体资源

以后你做别的功能,也基本都围绕这个目录展开。


11.3 assets/scenes/

这个目录专门放场景文件。

目前你最重要的场景是:

  • Main.scene

它的作用是:

  • 保存主游戏场景
  • 保存 Canvas
  • 保存玩家、UI、管理器等场景节点

为什么要单独放到 scenes/

  • 场景文件是项目结构的核心入口
  • 单独管理后,不容易和图片、脚本混在一起

以后如果你继续扩展,还可能新增:

  • Start.scene:开始界面
  • Select.scene:关卡选择
  • Game.scene:正式战斗场景

11.4 assets/scripts/

这个目录专门放代码脚本。

你现在的脚本基本都在这里,例如:

  • PlayerController.ts
  • PlayerShoot.ts
  • Bullet.ts
  • Enemy.ts
  • EnemySpawner.ts
  • GameManager.ts
  • Explosion.ts

这个目录里的每个脚本,都代表“某个功能逻辑”。

你可以这样理解:

  • 场景节点决定“谁在场上”
  • 脚本决定“这些东西会做什么”

为什么要把脚本集中放这里:

  • 以后好找
  • 不容易和图片资源混在一起
  • 代码量变多后更方便管理

如果项目后面变大,可以继续细分成:

scripts/
  player/
  enemy/
  bullet/
  ui/
  manager/
  effect/

例如:

  • player/PlayerController.ts
  • player/PlayerShoot.ts
  • enemy/Enemy.ts
  • enemy/EnemySpawner.ts
  • manager/GameManager.ts

你现在先不用急着拆这么细,但要知道后面可以这么整理。


11.5 assets/prefabs/

这个目录专门放预制体。

目前你已经有:

  • Bullet.prefab
  • Enemy.prefab
  • Explosion.prefab

为什么这些对象适合做成预制体:

  • 它们会被反复生成
  • 不是只在场景里出现一次
  • 运行时需要动态创建

你可以把预制体理解成“模板”:

  • Bullet.prefab 是子弹模板
  • Enemy.prefab 是敌机模板
  • Explosion.prefab 是爆炸模板

运行时不是重新手搓一个节点,而是从这个模板复制出新对象。

以后还适合放进 prefabs/ 的内容有:

  • 道具预制体
  • Boss 预制体
  • 敌机子弹预制体
  • 开始界面弹窗预制体

11.6 assets/textures/

这个目录专门放图片资源。

比如你目前的:

  • 飞机图
  • 敌机图
  • 爆炸图

为什么单独放:

  • 所有图片集中管理
  • 导入资源时不容易乱
  • 做替换美术时更方便

这里有个你已经接触到的重要点:

图片导入后,Cocos 不只是给你一个原图,还会生成可供 Sprite 使用的 spriteFrame

所以以后你给 Sprite 指定图片时,要优先用:

  • spriteFrame

而不是:

  • 原始 texture

如果以后图片更多,也可以继续细分:

textures/
  player/
  enemy/
  bullet/
  effect/
  ui/

例如:

  • textures/player/player_1.png
  • textures/enemy/enemy_small.png
  • textures/effect/explosion_1.png

11.7 一个资源从导入到使用的完整过程

你可以用“敌机”为例理解整条链路:

第一步:图片进入 textures/

例如:

  • assets/textures/enemy.png

第二步:在场景中创建节点

例如创建 Enemy 节点,并加上 Sprite

第三步:把图片的 spriteFrame 拖到 Sprite Frame

这样节点就显示出敌机图片了。

第四步:给节点挂脚本和组件

例如:

  • Enemy.ts
  • BoxCollider2D
  • RigidBody2D

第五步:把节点拖进 prefabs/

这样就得到:

  • Enemy.prefab

第六步:在生成器脚本里引用这个预制体

例如在 EnemySpawner.ts 中通过:

  • @property(Prefab)
  • instantiate(enemyPrefab)

动态创建敌机。

这就是一个资源从“图片”变成“可运行游戏对象”的完整路径。


11.8 目录结构和场景结构不是一回事

这是一个很重要、很容易混淆的点。

目录结构

指的是硬盘上的文件分类。

例如:

  • assets/scripts/
  • assets/prefabs/
  • assets/textures/

场景结构

指的是 Hierarchy 里的节点父子关系。

例如:

  • Canvas
  • Player
  • GameManager
  • GameOverPanel

所以你要把这两件事分开理解:

  • 文件放在哪里,是目录结构问题
  • 节点挂在哪里,是场景结构问题

11.9 你现在项目中的“谁放在哪里”

为了帮助你更快记住,我把你现在项目里的核心内容重新对应一次。

放在 scenes/

  • Main.scene

放在 scripts/

  • 玩家移动脚本
  • 玩家射击脚本
  • 子弹脚本
  • 敌机脚本
  • 刷怪脚本
  • 游戏管理脚本
  • 爆炸脚本

放在 prefabs/

  • 子弹模板
  • 敌机模板
  • 爆炸模板

放在 textures/

  • 玩家飞机图片
  • 敌机图片
  • 爆炸序列图

放在 Hierarchy 里的场景节点

  • Canvas
  • Player
  • EnemySpawner
  • GameManager
  • ScoreLabel
  • HpLabel
  • GameOverPanel

这样一对应,你以后就不容易乱了。


11.10 为什么现在这样整理对你最合适

因为你现在还是初学阶段,这种结构有三个好处:

第一,容易找

你要找脚本,就进 scripts/

你要找图片,就进 textures/

你要找可重复生成的模板,就进 prefabs/

第二,容易讲清楚

你以后自己复盘时,很容易说清楚:

  • 这个功能对应哪个脚本
  • 这个对象对应哪个预制体
  • 这个外观对应哪张图片

第三,后面容易扩展

以后你新增双发子弹、Boss、道具、音效时,完全可以在现有结构上继续长,不需要推倒重来。


11.11 下一步如果继续扩展,目录可以怎么升级

当项目内容更多时,你可以把结构逐渐升级成这样:

assets/
  scenes/
    Main.scene
    Start.scene
  scripts/
    player/
      PlayerController.ts
      PlayerShoot.ts
    enemy/
      Enemy.ts
      EnemySpawner.ts
    bullet/
      Bullet.ts
    manager/
      GameManager.ts
    effect/
      Explosion.ts
    ui/
      StartPanel.ts
      GameOverPanel.ts
  prefabs/
    player/
    enemy/
    bullet/
    effect/
    ui/
  textures/
    player/
    enemy/
    bullet/
    effect/
    ui/
  audio/
    bgm/
    sfx/

这是更接近正式项目的写法。

你现在不用一步到位,但可以先知道方向。


11.12 这一节的核心记忆法

你可以用一句很简单的话记住:

  • scenes 放场景
  • scripts 放逻辑
  • prefabs 放模板
  • textures 放图片

如果以后又忘了,就先回到这四个词。