本文由“壹伴编辑器”提供技术支
前言
今天我们介绍的项目,是各位大多数人都有玩过,就算没玩过也不可能没听过的一款经典游戏,那就是 贪 吃 蛇 。
制作一个贪吃蛇游戏也有许多不同的方法,这篇文章将向大家展示的是如何使用 链表 的原理来制作一个贪吃蛇。
前排提示:点击文章底部 阅读原文 获取完整项目
本文由“壹伴编辑器”提供技术支
效果展示
吃吃吃
正文
整体思路
1. 首先还是需要简单说下链表(LinkedList)的定义:链表实际上是一种链式数据结构,链表中的每一个数据节点都保存了其下一个节点的指针(也就是引用)。
2. 蛇就是由一节一节的身体构成的(当然头也是身体的一部分),我们控制头部的移动,头部会带着第一节身体移动,然后每一节身体移动的时候都会带着下一节身体一起移动(禁止套娃)。
代码实现
提示:这里只展示最主要部分的代码,完整项目点击文章底部 阅读原文 获取
1. 首先我们先实现一个链表节点的组件 Body 。该组件需要保存下一个节点的引用和上一个位置,以及设置下一节点和移动的函数:
const { ccclass, property } = cc._decorator;
@ccclass
export default class Body extends cc.Component {
@property({ type: Body, tooltip: '下一节身体' })
protected nextBody: Body = null;
protected lastPos: cc.Vec2 = null; // 上一个位置
/**
* 设置下一节身体
* @param body 节点
*/
public setNextBody(body: Body) {
this.nextBody = body;
body.node.setPosition(this.lastPos);
}
/**
* 移动该节点
* 同时让下一个节点(如果有的话)移动到该节点之前的位置
* @param nextPos 目标位置
*/
public move(nextPos: cc.Vec2) {
// 保存当前位置
this.lastPos = this.node.getPosition();
// 自身移动到新的位置
this.node.setPosition(nextPos);
// 让下一个节点移动到之前的位置
if (this.nextBody) this.nextBody.move(this.lastPos);
}
}
2. 实现蛇头部的组件 Head ,该组件继承于 Body ,所以 Head 拥有 Body 的所有属性以及函数。同时我们需要给 Head 新增一些头部特有的功能,比如移动和吃:
import Body from "./Body";
import PoolManager from "../manager/PoolManager";
import { GameEvent } from "../../eazax-ccc/core/GameEvent";
const { ccclass, property } = cc._decorator;
export enum Direction {
Up = 1, // 上
Down, // 下
Left, // 左
Right // 右
}
@ccclass
export default class Head extends Body {
private timer: number = 0; // 计时器
private moveInterval: number = 0.2; // 移动间隔
private moveDistance: number = 20; // 移动距离
private bodyList: Body[] = []; // 身体节点集合
private curDirection: Direction = Direction.Up; // 当前移动的方向
private nextDirection: Direction = Direction.Up; // 下次移动的方向
private isAlive: boolean = false; // 是否活着
private static instance: Head = null; // 实例
/**
* 获取头部位置
*/
public static get pos() { return this.instance.node.getPosition(); }
protected onLoad() {
Head.instance = this;
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
GameEvent.on('gameinit', this.onGameInit, this);
GameEvent.on('gamestart', this.onGameStart, this);
}
protected onDestroy() {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
GameEvent.off('gameinit', this.onGameInit, this);
GameEvent.off('gamestart', this.onGameStart, this);
}
protected update(dt: number) {
if (!this.isAlive) return;
// 计时
this.timer += dt;
if (this.timer >= this.moveInterval) {
this.timer -= this.moveInterval;
// 移动到下一个位置
switch (this.nextDirection) {
case Direction.Up:
this.curDirection = Direction.Up;
this.move(cc.v2(this.node.x, this.node.y + this.moveDistance));
break;
case Direction.Down:
this.curDirection = Direction.Down;
this.move(cc.v2(this.node.x, this.node.y - this.moveDistance));
break;
case Direction.Left:
this.curDirection = Direction.Left;
this.move(cc.v2(this.node.x - this.moveDistance, this.node.y));
break;
case Direction.Right:
this.curDirection = Direction.Right;
this.move(cc.v2(this.node.x + this.moveDistance, this.node.y));
break;
}
}
}
/**
* 初始化
*/
private onGameInit() {
this.isAlive = false;
this.node.setPosition(0, 0);
this.nextDirection = Direction.Up;
for (let i = 0; i < this.bodyList.length; i++) {
PoolManager.put(this.bodyList[i].node);
}
this.bodyList = [];
this.nextBody = null;
}
/**
* 动起来
*/
private onGameStart() {
this.onGameInit();
this.isAlive = true;
}
/**
* 吃吃吃,长长长
*/
private eat(food: cc.Node) {
cc.log('eat');
// 食物吃掉
PoolManager.put(food);
// 获取一节身体
let node = PoolManager.get('body');
node.setParent(this.node.parent);
// 设置身体位置并保存身体的引用
let body = node.getComponent(Body);
if (this.bodyList.length > 0) this.bodyList[this.bodyList.length - 1].setNextBody(body);
else this.setNextBody(body);
this.bodyList.push(body);
// 发射事件
GameEvent.emit('snakeeat');
}
/**
* GG
*/
private die() {
cc.log('GG');
this.isAlive = false;
GameEvent.emit('snakedie');
}
/**
* 键盘回调
* @param event 按键事件
*/
private onKeyDown(event: any) {
switch (event.keyCode) {
case cc.macro.KEY.up:
if (this.curDirection !== Direction.Down) this.nextDirection = Direction.Up;
break;
case cc.macro.KEY.down:
if (this.curDirection !== Direction.Up) this.nextDirection = Direction.Down;
break;
case cc.macro.KEY.left:
if (this.curDirection !== Direction.Right) this.nextDirection = Direction.Left;
break;
case cc.macro.KEY.right:
if (this.curDirection !== Direction.Left) this.nextDirection = Direction.Right;
break;
}
}
/**
* 碰撞回调
* @param other
* @param self
*/
protected onCollisionEnter(other: cc.BoxCollider, self: cc.BoxCollider) {
switch (other.node.name) {
case 'food':
this.eat(other.node);
break;
case 'body':
case 'border':
this.die();
break;
}
}
}
本文由“壹伴编辑器”提供技术支
结束语
以上皆为本皮皮的个人观点,小生不才,文采不足,如果写得不好还请各位多多包涵。如果有哪些地方说的不对,还请各位指出,希望与大家共同进步。
接下来我会持续分享自己所学的知识与见解,欢迎各位关注本公众号。
我们,下次见!
本文由“壹伴编辑器”提供技术支
扫描二维码
获取更多精彩
文弱书生陈皮皮
点击 阅读原文 获取完整项目