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 コマンド例の詳細な説明

推薦する

Windows での MySQL 5.7.18 インストール チュートリアル

この記事では、圧縮パッケージから MySQL をインストールする方法について説明します。 1. My...

JavaScriptの原理と方向性

これが何を指しているのかをどのように判断するのでしょうか? ①グローバル環境で呼び出された場合はwi...

DockerとVMwareの競合を解決する

1. Dockerの起動の問題:問題は解決しました: Hyper-V をオンにする必要があります (...

純粋な CSS 流星群の背景サンプルコード

GitHubアドレス、気に入ったらスターを付けてくださいプラグインのプレビューチュートリアルコード表...

共通要素のデフォルトのマージンとパディング値に関する議論

今日は、さまざまなブラウザでのデフォルト要素のマージン値が何であるかという問題について説明しました。...

枠線や境界線のない iframe を使用するための完全ガイド (実践経験のまとめ)

<iframe src=”ページのURL” width=”100″ height=”30″ f...

Dockerコンテナの個別展開のためのLNMPの実装

1. 環境整備各コンテナの IP アドレス: nginx: 172.16.10.10マイSQL: 1...

vue-routerフック関数はルーティングガードを実装します

目次概要グローバルフック関数ルーティング固有のフック関数コンポーネント内のフック関数概要ルートガード...

MySQL データベースの大文字と小文字の区別の問題

MySQL では、データベースはデータ ディレクトリ内のディレクトリに対応します。データベース内の各...

Angular環境構築と簡単な体験のまとめ

Angular入門Angular は、Google が開発したオープンソースの Web フロントエン...

CentOS 上での MySQL 5.6 のコンパイルとインストール、および複数の MySQL インスタンスのインストールの詳細な説明

--1. mysql用の新しいグループとユーザーを作成する # ユーザー追加 -M -s /sbin...

MyBatisインターセプターのページング機能を実装する方法

MyBatisインターセプターのページング機能を実装する方法序文:まず、実装原則についてお話しします...

Linux ソースコードからのソケット (TCP) クライアント側での接続の例の詳細な説明

序文著者は、アプリケーションからフレームワーク、オペレーティング システムに至るまで、あらゆるコード...

Vue+webrtc (Tencent Cloud) ライブブロードキャスト機能の実装実践

目次1. 生放送効果2. ライブストリーミングを開始する手順2.1 Tencent Web(高速ライ...

Vue のスロットリング関数使用時の落とし穴ガイド

序文一般的なビジネス シナリオでは、検索ボックスへの入力が完了した後、検索データを取得するために関連...