CocosCreatorゲームにおける魚群アルゴリズムの詳細な説明

CocosCreatorゲームにおける魚群アルゴリズムの詳細な説明

序文

最近CocosCreatorを学びたいと思ったので、エディターをダウンロードして起動しました。

誰もが知っているように、書くことで学ぶのが最も早い学習方法です。練習するにはデモを書く必要があります。では、何を書くべきでしょうか? 「インクシュリンプ オタマジャクシ探検」が今大人気と聞いたので、真似して(勉強するのが真似と言えるでしょうか?)似たようなゲームを書いてみようと思います!

(「インクシュリンプ探検オタマジャクシ」では、魚の位置は固定されています。一定の数の魚に達すると、プレーヤーはアップグレードし、大きな魚の群れは存在しません。このプロジェクトは実際にはそれとは異なります。アップグレードや進化はありませんが、大きな魚の群れが存在します。各魚は固定された位置になく、独自の移動ロジックを持っています。実際、それは別のゲームに似ていますが、それが何と呼ばれているのかわかりません...)

エフェクト表示:

文章

まず、プレーヤーを作成します。

使用されている画像リソースは、CocosCreator の公式デモからの画像です。公式デモから学びました。魚の画像を探すのが面倒だったので、画像をそのまま使用しました。このプロジェクトでは現在、2 枚の写真のみを使用しています。

プレイヤーができたら、プレイヤー制御スクリプトを記述する必要があります。方向をクリックすると、プレイヤーはその方向に移動し続けます。次に、まずプレイヤーがクリックした位置を取得し、次にプレイヤーが移動する方向を計算する必要があります。これは GameManager に記述するため、新しいスクリプト GameManager を作成し、このスクリプトを Canvas に貼り付けます。

まず、プレーヤー ノードと方向ベクトルの 2 つの変数を定義します。

@property(cc.Node)
プレーヤー: cc.Node = null;
cc.Vec2 = cc.Vec2.ZERO; を呼び出します。

道順を取得する方法:

getClickDir(イベント) {
    pos: cc.Vec2 = event.getLocation(); とします。
    // ローカル座標に変換します。let localPos = this.node.convertToNodeSpaceAR(pos);
    playerPos: cc.Vec2 = new cc.Vec2( とします。
        this.player.position.x、
        このプレイヤーの位置
    );

    len = localPos.sub(playerPos).mag(); とします。

    this.dir.x = localPos.sub(playerPos).x / len;
    this.dir.y = localPos.sub(playerPos).y / len;
}

このメソッドは onMouseDown と onMouseMove で呼び出されます。

onMouseDown(イベント) {
    イベントの取得ボタン() が cc.Event.EventMouse.BUTTON_LEFT の場合 {
        this.getClickDir(イベント);
    }
}

onMouseMove(イベント) {
    イベントの取得ボタン() が cc.Event.EventMouse.BUTTON_LEFT の場合 {
        this.getClickDir(イベント);
    }
}

オンロード() {
    cc.director.getCollisionManager().enabled = true;
    cc.director.getPhysicsManager().enabled = true;

    this.node.on(cc.Node.EventType.MOUSE_DOWN、 this.onMouseDown、 this);
    this.node.on(cc.Node.EventType.MOUSE_MOVE、this.onMouseMove、this);
}

onDestroy() {
    this.node.off(cc.Node.EventType.MOUSE_DOWN、this.onMouseDown、this);
    this.node.off(cc.Node.EventType.MOUSE_MOVE、this.onMouseMove、this);
}

方向ベクトルを使用して、プレーヤーを移動できます。新しい FishPlayer スクリプトを作成します。

プレイヤーが走り回らないようにするために、まず壁を構築します。

壁に物理的な衝突ボディを追加します。

次に、FishPlayer スクリプトの作成を開始します。まず、使用する変数を定義します。

@property(cc.Node)
カメラ: cc.Node = null;

@property(cc.Node)
ゲームマネージャー: cc.Node = null;

ゲーム: GameManager;
速度: 数値 = 170;
速度: cc.Vec3 = cc.Vec3.ZERO;

onLoad() でゲームに値を割り当てます。

オンロード() {
    this.game = this.gameManager.getComponent("GameManager");
}

光線を通して境界を検出し、プレイヤーが移動できるかどうかを判断する方法:

移動できる() {
    var フラグ: ブール値 = true;
    //前方に障害物があります var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);
    var endPos = pos.add(this.node.up.mul(40));
    var ヒット: cc.PhysicsRayCastResult[] = cc.director
        .getPhysicsManager()
        .rayCast() 関数
            新しいcc.Vec2(pos.x, pos.y)、
            新しいcc.Vec2(endPos.x, endPos.y)、
            cc.RayCastType.すべて
        );
    ヒットの長さが0より大きい場合
        フラグ = false;
    }
    フラグを返します。
}

アップデートでプレイヤーの動きを制御:

更新(dt) {
    (this.game.dir.mag() < 0.5)の場合{
        this.velocity = cc.Vec3.ZERO;
        戻る;
    }

    vx: number = this.game.dir.x * this.speed; とします。
    vy: number = this.game.dir.y * this.speed; とします。

    this.velocity = 新しい cc.Vec3(vx, vy);
    // 移動する場合 (this.canMove()) {
        this.node.x += vx * dt;
        this.node.y += vy * dt;
    }

    //カメラは this.camera.setPosition(this.node.position); に従います。

    // 移動方向に回転します。let hudu = Math.atan2(this.game.dir.y, this.game.dir.x);
    角度をhudu * (180 / Math.PI)とします。
    角度 = 360 - 角度 + 90;
    this.node.angle = -angle;
}

プレイヤーの移動ロジックが記述されたので、魚群を記述しましょう。

新しい FishGroupManager スクリプトと FishGroup スクリプトを作成し、FishGroupManager を Canvas にハングし、FishGroup をプレーヤーにハングします。

すべてのグループを管理するために、 FishGroupManager に静的な fishGroups 変数が定義されています (シーンには複数のプレイヤーと複数の魚群が存在する可能性があり、現在はプレイヤーが 1 人だけなので、将来の拡張に便利です)。

static fishGroups: FishGroup[]; //すべてのグループ

グループにグループを追加する静的メソッドを次に示します。

静的AddGroup(グループ: FishGroup) {
    this.fishGroups == null の場合、 this.fishGroups = new Array();

    this.fishGroups.indexOf(group) == -1 の場合、 this.fishGroups.push(group);
}

グループを取得するための別の静的メソッドを次に示します (インデックスで取得します)。

静的GetFishGroup(インデックス: 数値) {
    (var i = 0; i < this.fishGroups.length; i++) の場合
        if (this.fishGroups[i].groupID == index) の場合、this.fishGroups[i] を返します。
}

FishGroupManager が記述されました。次に、上記で使用したグループ ID と魚のグループ配列を定義する FishGroup を記述します。

groupID: number = 0; //グループID    
fishArr: cc.Component[] = 新しい配列<cc.Component>();

onLoad で fishGroups に自身を追加します:

オンロード() {
    FishGroupManager.AddGroup(これ);
}

魚の群れはありますが、その中に魚はいません。そのため、魚を捕まえる方法が必要です。

catchFish(魚) {
    this.fishArr.push(魚);
}

使用するパラメータをさらにいくつか定義すると、FishGroup は完成します。

最小距離を維持: 数値 = 80;
最大距離を維持: 数値 = 100;
keepWeight: number = 1; //メンバーは距離を維持し、距離の重みを維持 moveWeight: number = 0.8; //メンバーは重みを移動

ここでハイライト、つまり群れの中の他の小魚の動きのロジックが登場します。

プレーヤーをコピーし、マウントされた FishPlayer および FishGroup スクリプトを削除して、fish という名前を付けます。これが私たちの小さな魚です。これをプレハブにします。次に、プレイヤーと通常の魚にアタッチされる新しい FishBehaviour スクリプトを作成します。

まず、「魚を捕まえる」機能を実装します。プレイヤーが小魚に近づくと、小魚は捕まえられ、プレイヤーの魚群の一員になります。

関連する変数を定義します。

@property(cc.Node)
ゲームマネージャー: cc.Node = null;
ゲーム: GameManager;
isPicked: ブール値 = false;
pickRadius: number = 50; // グラブ距離 groupId: number = -1; // グループ ID
私のグループ: FishGroup;

同様に、onLoad() で game に値を割り当てます。

オンロード() {
    this.game = this.gameManager.getComponent(GameManager);
}

プレイヤーまでの距離を決定する方法:

プレイヤーの距離を取得する() {
    dist = this.node.position.sub(this.game.player.position).mag() とします。
    dist を返します。
}

魚の群れに加わる方法:

選択された() {
    //グループを設定
    このグループIDは、このゲームプレイヤーのグループIDを取得します。
    this.myGroup = FishGroupManager.GetFishGroup(this.groupId);

    if (this.myGroup != null) {
        this.myGroup.catchFish(これ);
        this.isPicked = true;
    }
}

電話での更新:

更新(dt) {
    if (this.isPicked) {
        //魚の群れと一緒に移動する}
    それ以外 {
        this.getPlayerDistance() が this.pickRadius より小さい場合、
            this.onPicked();
        }
    }
}

さて、小魚は魚の群れの中にいます。小魚はどうやって魚の群れと一緒に動くのでしょうか?

ここでの主なポイントは 2 つあります。

1. 魚は周囲の「隣の魚」と一緒に動きます

2. 小魚同士の距離を保ち、密集させないようにする

したがって、小魚の周囲の一定範囲内の魚の移動ベクトルの平均値を計算する必要があります。これだけでは十分ではありません。また、「混雑している」かどうかを判断する必要があります。「混雑している」場合は、遠ざかる傾向を追加します。遠すぎる場合は、近づく傾向を追加します。次に、それぞれに重みを掛けて合計し、必要なベクトルを取得します。コードは次のとおりです。

変数を定義します。

移動速度: 数値 = 170;
rotateSpeed: number = 40; // 移動回転速度 neighborRadius: number = 500; // 500 未満の距離は隣接とみなされます speed: number = 0;
現在の速度: 数値 = 0;
私の動き: cc.Vec3 = cc.Vec3.ZERO;

平均ベクトルを求めます。

GetGroupMovement() {
        変数 v1: cc.Vec3 = cc.Vec3.ZERO;
        変数 v2: cc.Vec3 = cc.Vec3.ZERO;
 
        (var i = 0; i < this.myGroup.fishArr.length; i++) {
            var otherFish: FishBehaviour = this.myGroup.fishArr[i].getComponent(
                魚の行動
            );
 
            var dis = this.node.position.sub(otherFish.node.position); //距離//隣接ノードではない if (dis.mag() > this.neighborRadius) {
                続く;
            }
 
            cc.Vec3 を cc.Vec3.ZERO に変換します。
            //最大距離より大きい場合は閉じます if (dis.mag() > this.myGroup.keepMaxDistance) {
                1. 正規化後の値 (dis.mag() が 1 の場合) を、 2. 正規化後の値 (dis.mag() が 1 の場合) を、 3. 正規化後の値 (dis.mag() が 1 の場合) を、 4. 正規化後の値 (dis.mag() が 1 の場合) を、 5. 正規化後の値 (dis.mag() が 1 の場合) を、 6. 正規化後の値 (dis.mag
            }
            // 最小距離より小さい、遠い else if (dis.mag() < this.myGroup.keepMinDistance) {
                1. 正規化後の値 (dis.mag() が 1 の場合) を、 2. 正規化後の値 (dis.mag() が 1 の場合) を、 3. 正規化後の値 (dis.mag() が 1 の場合) を、 4. 正規化後の値 (dis.mag() が 1 の場合) を、 5. 正規化後の値 (dis.mag() が 1 の場合) を、 6. 正規化後の値 (dis.mag
            } それ以外 {
                続く;
            }
 
            v1 = v1.add(v); // 周囲のユニットまでの距離 v2 = v2.add(otherFish.myMovement); // 周囲のユニットの移動方向}
 
        //重み係数を追加 v1 = v1.normalize().mul(this.myGroup.keepWeight);
        v2 = v2.normalize().mul(this.myGroup.moveWeight);
        var ret = v1.add(v2);
        ret を返します。
    }

これでアップデートを完了できます。

更新(dt) {
        //魚の群れと一緒に移動する if (this.isPicked) {
            var direction = cc.Vec3.ZERO;
            if (this.node.name != "プレイヤー") {
                方向 = direction.add(this.GetGroupMovement());
            }
 
            this.speed = cc.misc.lerp(this.speed、this.moveSpeed、2 * dt);
            this.Drive(direction, this.speed, dt); // 移動}
        //キャプチャelse {
            this.getPlayerDistance() が this.pickRadius より小さい場合、
                this.onPicked();
            }
        }
    }

Drive() メソッド:

ドライブ(方向: cc.Vec3、spd: 数値、dt) {
    var finialDirection: cc.Vec3 = direction.normalize();
    var finialSpeed: 数値 = spd;
    var finialRotate: 数値 = 0;
    var rotateDir: number = cc.Vec3.dot(finialDirection, this.node.right);
    var forwardDir: number = cc.Vec3.dot(finialDirection, this.node.up);

    (forwardDir < 0)の場合{
        rotateDir = Math.sign(rotateDir);
    }

    //手ぶれ防止 if (forwardDir < 0.98) {
        finialRotate = cc.misc.clampf(
            回転方向 * 180、
            -this.rotateSpeed、
            this.rotateSpeed
        );
    }

    finialSpeed * = cc.misc.clamp01(direction.mag());
    finialSpeed * = cc.misc.clamp01(1 - Math.abs(rotateDir) * 0.8);
    (Math.abs(finialSpeed) < 0.01)の場合{
        最終速度 = 0;
    }

    // 移動する場合 (this.canMove()) {
        this.node.x += this.node.up.x * finialSpeed ​​* dt;
        this.node.y += this.node.up.y * finialSpeed ​​* dt;
    }

    //回転 var angle1 = finialRotate * 8 * dt;
    var angle2 = this.node.angle - angle1;
    角度2は360度です。

    this.currentSpeed ​​= finialSpeed;
    this.myMovement = direction.mul(finialSpeed);
}

移動できる() {
    var フラグ: ブール値 = true;
    //前方に障害物があります var pos = this.node.convertToWorldSpaceAR(cc.Vec3.ZERO);
    var endPos = pos.add(this.node.up.mul(40));
    var ヒット: cc.PhysicsRayCastResult[] = cc.director
        .getPhysicsManager()
        .rayCast() 関数
            新しいcc.Vec2(pos.x, pos.y)、
            新しいcc.Vec2(endPos.x, endPos.y)、
            cc.RayCastType.すべて
        );
    ヒットの長さが0より大きい場合
        フラグ = false;
    }
    フラグを返します。
}

上記は、CocosCreatorゲームの魚群アルゴリズムの詳細な説明です。CocosCreator魚群アルゴリズムの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • CocosCreatorでクールなレーダーチャートを描く方法
  • CocosCreator MVCアーキテクチャの詳細な説明
  • CocosCreatorメッセージ配信メカニズムの詳細な説明
  • CocosCreator 入門チュートリアル: ネットワーク通信
  • CocosCreatorでWeChatゲームを作成する方法
  • CocosCreator システムイベントがどのように生成され、トリガーされるかについての詳細な説明
  • CocosCreator Huarongdaoデジタルパズルの詳しい説明
  • CocosCreator最適化DrawCallの詳細な説明
  • CocosCreatorでリストを作成する方法

<<:  MySQL 5.7 データベースのインストール手順の個人的な要約

>>:  Linux での tcpdump コマンド例の詳細な説明

推薦する

CentOS7.4 に MySQL 5.7.26 をインストールするための詳細なチュートリアル

CentOS にはデフォルトで MariaDB がインストールされていますが、これは MySQL の...

React 星評価コンポーネントの実装

要件は、製品の評価データを渡すことであり、ページには対応する星の数が表示されます。 1. 異なる評価...

MySQLトリガーの使用

目次1. トリガーの紹介1. トリガーとは何ですか? 2. トリガーの特徴2. トリガーを作成する1...

MySQL 8.0 以降の一般的なコマンドの詳細な説明

リモートアクセスを有効にする次のコマンドを実行して、root ユーザーのリモート アクセス権を有効に...

ダイナミックな波効果を実現するSVG+CSS3

ベクトル波 <svg viewBox="0 0 560 20" class...

純粋なHTML+CSSでタイピング効果を実現

この記事は主に、一定の参考値を持つ純粋な HTML + CSS によって実現されるタイピング効果を紹...

MySQLはIDに適切なデータ型を選択します

目次分散IDソリューションの概要データベース自動増分IDデータベースマルチマスターモード数値セグメン...

CSS で隠し要素を実現する 7 つの興味深い方法

序文非表示要素の 3 つの属性である表示、可視性、不透明度の類似点と相違点は、フロントエンドの就職面...

HTML での select optgroup タグの使用の概要

時々、選択した内容をグループ化する必要があります。以前はプログラム制御を使用していました。今日、se...

JSを段階的に学ぶ方法についての簡単な説明

目次概要1. jsの位置づけを明確に理解する2. 明確な学習パス3. 自己規律と粘り強さ4. 練習し...

Javascript フロントエンド最適化コード

目次if判定の最適化1. 最も簡単な方法:判断2. より良い方法: スイッチ3. より良いアプローチ...

JS での new の手書き実装

目次1 新しいオペレータの紹介2 新しいものは何をしましたか? 3 新しい演算子の実装をシミュレート...

MySQLがトランザクション分離を実装する方法の簡単な分析

目次1. はじめに2. RC および RR 分離レベル2.1. RRトランザクション分離レベルでのク...

FileZilla を使用して FTP ファイル サービスを素早く構築する方法

ファイルの保存とアクセスを容易にするために、FTPサービスが特別に構築されています。 FTP サーバ...

React 高階コンポーネント HOC 使用方法の概要

HOCを紹介する一文高階コンポーネント (HOC) とは何ですか? 公式ドキュメントによると、「高階...