B站影片:【Creator3】好玩的編隊程式碼 魔性排列停不下來 附源碼及出處

 

這次帶來一個有趣的編隊程式碼,簡單的演算法得到令人驚嘆的編隊隊形,嘆為觀止,前幾天刷影片的時候看到一個有趣的展示,來自youtube大神:Tarodev的隊形計算展示《 Fun with Formations – Game Dev Adventure

他的是Unity實現的,我用最新發布的Creator 3.3版本製作了一個,影片如下:

參考資料://www.youtube.com/watch?v=NEFxWkTRVCc 

開發工具 CocosCreator 3.3.0

BGM: Jazzy Frenchy form bensound

 Creator3以後的引擎,對於3D方面的計算基本上和Unity很像了,轉譯C#程式碼並不難,原版程式碼在這裡:GitHub – Matthew-J-Spencer/Formations: Some simple scripts to create formations in Unity 

CocosCreator中的關鍵程式碼如下:

/** 編隊的基類 */
import { _decorator, Component, Node, Vec3, v3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('BaseFormation')
export class BaseFormation extends Component {
    @property
    noise: number = 0;
    @property
    Spread: number = 1;
    offsetPos:Vec3 = v3();
    EvaluatePoints(): Vec3[] {
        return null;
    }
    GetNoise(pos: Vec3): Vec3 {
        const noise = this.noise;
        const x = pos.x + Math.random() * noise - noise / 2;
        const z = pos.z + Math.random() * noise - noise / 2;
        pos.x = x;
        pos.z = z;
        return pos;
    }
}
/** 方形編隊組件腳本程式碼 */
import { _decorator, Component, Node, Vec3, v3 } from 'cc';
import { BaseFormation } from './BaseFormation';
const { ccclass, property } = _decorator;
@ccclass('BoxFormation')
export class BoxFormation extends BaseFormation {
    @property
    unitWidth: number = 5;
    @property
    unitDepth: number = 5;
    @property
    hollow: boolean = false;
    @property
    nthOffset: number = 0;
    EvaluatePoints(): Vec3[] {
        const ret: Vec3[] = [];
        const middleOffset = v3((this.unitWidth - 1) * 0.5, 0, (this.unitDepth - 1) * 0.5);
        for (let x = 0; x < this.unitWidth; x++) {
            for (let z = 0; z < this.unitDepth; z++) {
                if (this.hollow && x != 0 && x != this.unitWidth - 1 && z != 0 && z != this.unitDepth - 1) continue;
                let pos = v3(x + (z % 2 == 0 ? 0 : this.nthOffset), 0, z);

                pos = pos.subtract(middleOffset);

                pos = this.GetNoise(pos);

                pos = pos.multiplyScalar(this.Spread);
                pos.add(this.offsetPos);
                ret.push(pos);
            }
        }
        return ret;
    }
}
/** 放射編隊組件腳本程式碼 */
import { _decorator, Component, Node, Vec3, v3 } from 'cc';
import { BaseFormation } from './BaseFormation';
const { ccclass, property } = _decorator;
@ccclass('RadialFormation')
export class RadialFormation extends BaseFormation {
    @property
    amount: number = 10;
    @property
    radius: number = 1;
    @property
    radiusGrowthMultiplier: number = 0;
    @property
    rotations: number = 1;
    @property
    rings: number = 1;
    @property
    ringOffset: number = 1;
    @property
    nthOffset: number = 1;
    EvaluatePoints(): Vec3[] {
        const ret: Vec3[] = [];
        let amountPerRing = this.amount / this.rings;
        let ringOffset = 0;
        for (var i = 0; i < this.rings; i++) {
            for (var j = 0; j < amountPerRing; j++) {
                let angle = j * Math.PI * (2 * this.rotations) / amountPerRing + (i % 2 != 0 ? this.nthOffset : 0);

                let radius = this.radius + ringOffset + j * this.radiusGrowthMultiplier;
                let x = Math.cos(angle) * radius;
                let z = Math.sin(angle) * radius;

                let pos = new Vec3(x, 0, z);

                pos = this.GetNoise(pos);

                pos = pos.multiplyScalar(this.Spread);

                pos.add(this.offsetPos);
                ret.push(pos);
            }
            ringOffset += this.ringOffset;
        }
        return ret;
    }
}

以上便是CocosCreator3版本的核心計算程式碼了,可以自行貼在編隊節點上,人物控制應該是由特定的渲染程式碼完成。

編隊展示處理程式碼

/**
 * 編隊渲染展示的程式碼,掛在到你想展示的節點容器上,配合對應的Formation組件
 * 請自行構建Actor角色組件用於處理人物邏輯,實現removeSelf和moveTo
 * 在合適的時機處理編隊的刷新,比如編隊參數變化、隊伍移動的時候
 */
import { _decorator, Component, Node, Prefab, instantiate, director } from 'cc';
import { Actor } from '../actors/Actor';
import { BaseFormation } from '../formations/BaseFormation';
const { ccclass, property } = _decorator;
 
@ccclass('FormationShow')
export class FormationShow extends Component {
    @property(Prefab)
    actor: Prefab = null;
    start() {
        this.FormationUpdate();
    }
    FormationUpdate(prop?: string, value?: number) {
        const formation = this.getComponent(BaseFormation);
        if (formation) {
            if (prop) {
                const old = formation[prop];
                formation[prop] = value;
                if (old == formation[prop]) {
                    return;
                }
            }
            const points = formation.EvaluatePoints();
            for (let i = this.node.children.length - 1; i >= points.length; i--) {
                this.node.children[i].getComponent(Actor).removeSelf();
            }
            const len = this.node.children.length;
            for (let i = 0; i < points.length; i++) {
                if (i < points.length && i >= len) {
                    let actor = null;
                    if (len > 0) {
                        actor = this.node.children[len - 1];
                    } else {
                        actor = this.actor;
                    }
                    const a = instantiate(actor);
                    this.node.addChild(a);
                }
                this.node.children[i].getComponent(Actor).moveTo(points[i]);
            }
        }
    }
}

結語

影片中的源碼工程已經發布到Cocos的官方商店,有需要的自取
//store.cocos.com/app/detail/3210