部队战争

部队战争

是一个用于《剑与魔法》的部队agar.io模组

敌对 AI

using UnityEngine;
using UnityEngine.AI;

public class EnemyAI : MonoBehaviour
{
    public NavMeshAgent agent;

    [Header("法球搜索")]
    public float searchRange = 100f;

    [Header("法球收集")]
    public float collectDistance = 2f;
    private Transform targetOrb;

    private OrbCollector collector;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();

        collector = GetComponent<OrbCollector>();

        if(collector == null)
        {
            Debug.LogWarning(
            "敌人需要一个 OrbCollector 组件!");
        }

        InvokeRepeating(
        "FindOrb",
        0,
        2);
    }

    void Update()
    {
        if(targetOrb != null)
        {
            agent.SetDestination(
            targetOrb.position);

            float distance =
            Vector3.Distance(
            transform.position,
            targetOrb.position);

            if(distance <= collectDistance)
            {
                CollectOrb();
            }
        }
    }

    void FindOrb()
    {
        GameObject[] orbs =
        GameObject.FindGameObjectsWithTag("Orb");

        float closest =
        Mathf.Infinity;

        GameObject best = null;

        foreach(GameObject orb in orbs)
        {
            float distance =
            Vector3.Distance(
            transform.position,
            orb.transform.position);

            if(distance < closest &&
            distance <= searchRange)
            {
                closest = distance;
                best = orb;
            }
        }

        if(best != null)
        {
            targetOrb =
            best.transform;
        }
    }

    void CollectOrb()
    {
        if(collector != null)
        {
            collector.CollectOrb(1);
        }

        Destroy(
        targetOrb.gameObject);

        targetOrb = null;
    }
}

end

敌对生成

using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    public GameObject enemyPrefab;

    public Terrain terrain;

    public int enemyAmount = 50;

    void Start()
    {
        SpawnEnemies();
    }

    void SpawnEnemies()
    {
        TerrainData data =
        terrain.terrainData;

        Vector3 terrainPos =
        terrain.transform.position;

        for(int i = 0; i < enemyAmount; i++)
        {
            float x =
            Random.Range(
            0,
            data.size.x);

            float z =
            Random.Range(
            0,
            data.size.z);

            float y =
            data.GetHeight(
            (int)x,
            (int)z);

            Vector3 spawn =
            new Vector3(
            x,
            y,
            z)
            + terrainPos;

            Instantiate(
            enemyPrefab,
            spawn,
            Quaternion.identity);
        }
    }
}

end

法球

using UnityEngine;
public class Orb : MonoBehaviour
{
    public int orbValue = 1;
    public float rotateSpeed = 100f;

    private void Update()
    {
        transform.Rotate(Vector3.up * rotateSpeed * Time.deltaTime);
    }

    private void OnTriggerEnter(Collider other)
    {
        OrbCollector collector = other.GetComponent<OrbCollector>();

        if (collector != null)
        {
            collector.CollectOrb(orbValue);
            Destroy(gameObject);
        }
    }
}

end

法球收集器

using UnityEngine;

public class OrbCollector : MonoBehaviour
{
    [Header("收集器")]
    public string collectorName;

    [Header("法球存储")]
    public int orbs = 0;

    [Header("部队生成")]
    public GameObject troopPrefab;
    public Transform troopSpawnPoint;
    public int orbsPerTroop = 5;

    public int troopsSpawned = 0;

    public void CollectOrb(int amount)
    {
        orbs += amount;

        Debug.Log(
        collectorName +
        " 拥有 " +
        orbs +
        " 个法球");

        CheckSpawn();
    }

    void CheckSpawn()
    {
        while(orbs >= orbsPerTroop)
        {
            SpawnTroop();

            orbs -= orbsPerTroop;
        }
    }

    void SpawnTroop()
    {
        if(troopPrefab == null)
        {
            Debug.LogWarning(
            "未分配部队预制体");
            return;
        }

        Vector3 spawnPosition;

        if(troopSpawnPoint != null)
        {
            spawnPosition =
            troopSpawnPoint.position;
        }
        else
        {
            spawnPosition =
            transform.position +
            transform.forward * 3;
        }

        GameObject troop =
        Instantiate(
        troopPrefab,
        spawnPosition,
        Quaternion.identity);

        TroopAI ai =
        troop.GetComponent<TroopAI>();

        if(ai != null)
        {
            ai.owner =
            transform;
        }

        troopsSpawned++;

        Debug.Log(
        "已生成部队 " +
        troopsSpawned);
    }
}

end

法球生成

using UnityEngine;

public class OrbSpawner : MonoBehaviour
{
    public GameObject orbPrefab;
    public Terrain terrain;

    public int amount = 200;

    public float heightOffset = 1f;

    void Start()
    {
        SpawnAllOrbs();
    }

    void SpawnAllOrbs()
    {
        if(terrain == null)
        {
            Debug.LogError("未分配地形");
            return;
        }

        TerrainData data = terrain.terrainData;

        Vector3 terrainPosition =
        terrain.transform.position;

        for(int i = 0; i < amount; i++)
        {
            float x =
            Random.Range(
            0,
            data.size.x);

            float z =
            Random.Range(
            0,
            data.size.z);

            float y =
            data.GetHeight(
            (int)x,
            (int)z);

            Vector3 position =
            new Vector3(
            x,
            y + heightOffset,
            z)
            + terrainPosition;

            Instantiate(
            orbPrefab,
            position,
            Quaternion.identity);
        }
    }
}

end

部队 AI

using UnityEngine;
using UnityEngine.AI;

public class TroopAI : MonoBehaviour
{
    [Header("所属")]
    public Transform owner;

    [Header("移动")]
    public NavMeshAgent agent;
    public float followDistance = 5f;
    public float updateRate = 0.5f;

    [Header("战斗")]
    public float health = 100;
    public float damage = 10;
    private float timer;

    void Start()
    {
        agent =
        GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        if(owner == null)
        {
            return;
        }

        timer += Time.deltaTime;

        if(timer >= updateRate)
        {
            FollowOwner();
            timer = 0;
        }
    }

    void FollowOwner()
    {
        float distance =
        Vector3.Distance(
        transform.position,
        owner.position);

        if(distance > followDistance)
        {
            agent.SetDestination(
            owner.position);
        }
        else
        {
            agent.ResetPath();
        }
    }

    public void TakeDamage(float amount)
    {
        health -= amount;

        if(health <= 0)
        {
            Destroy(gameObject);
        }
    }
}

end

视角

using UnityEngine;
using System.Collections.Generic;

public class EnemyWatchCamera : MonoBehaviour
{
    [Header("相机")]
    public Camera playerCamera;
    public Camera watchCamera;

    [Header("AI 设置")]
    public string aiTag = "Enemy";

    public float orbitDistance = 6f;
    public float orbitHeight = 3f;
    public float orbitSpeed = 120f;

    [Header("控制")]
    public KeyCode watchKey = KeyCode.V;
    public KeyCode nextTargetKey = KeyCode.C;

    private List<GameObject> targets = new List<GameObject>();

    private int currentTarget = 0;

    private bool watching = false;

    private float orbitAngle;

    void Start()
    {
        watchCamera.enabled = false;
        playerCamera.enabled = true;

        FindTargets();
    }

    void Update()
    {
        if(Input.GetKeyDown(watchKey))
        {
            ToggleWatch();
        }

        if(watching && Input.GetKeyDown(nextTargetKey))
        {
            NextTarget();
        }

        if(watching)
        {
            OrbitCamera();
        }
    }

    void FindTargets()
    {
        targets.Clear();

        GameObject[] enemies = GameObject.FindGameObjectsWithTag(aiTag);

        foreach(GameObject enemy in enemies)
        {
            targets.Add(enemy);
        }
    }

    void ToggleWatch()
    {
        watching = !watching;

        if(watching)
        {
            FindTargets();
            playerCamera.enabled = false;
            watchCamera.enabled = true;

            currentTarget = 0;
        }
        else
        {
            watchCamera.enabled = false;
            playerCamera.enabled = true;
        }
    }

    void NextTarget()
    {
        FindTargets();

        if(targets.Count == 0)
            return;

        currentTarget++;

        if(currentTarget >= targets.Count)
        {
            currentTarget = 0;
        }
    }

    void OrbitCamera()
    {
        if(targets.Count == 0)
            return;

        GameObject target = targets[currentTarget];

        if(target == null)
            return;

        orbitAngle += Input.GetAxis("Mouse X") * orbitSpeed * Time.deltaTime;

        Vector3 offset = new Vector3(
            Mathf.Sin(orbitAngle * Mathf.Deg2Rad) * orbitDistance,
            orbitHeight,
            Mathf.Cos(orbitAngle * Mathf.Deg2Rad) * orbitDistance
        );

        watchCamera.transform.position =
            target.transform.position + offset;

        watchCamera.transform.LookAt(
            target.transform.position + Vector3.up
        );
    }
}

end

方尖碑

using UnityEngine;
using System.Collections.Generic;

public class TerrainObeliskSpawner : MonoBehaviour
{
    [Header("地形")]
    public Terrain terrain;

    [Header("方尖碑预制体")]
    public GameObject obeliskPrefab;

    [Header("生成设置")]
    public int amountToSpawn = 20;

    public float minDistanceBetweenObelisks = 50f;
    public float spawnHeightOffset = 1f;

    [Header("生命周期")]
    public bool enableDecay = true;

    public float decayTime = 300f;
    public float respawnTime = 60f;

    private List<GameObject> spawnedObelisks = new List<GameObject>();

    void Start()
    {
        SpawnObelisks();
    }

    public void SpawnObelisks()
    {
        int attempts = 0;
        int maxAttempts = amountToSpawn * 50;

        while (spawnedObelisks.Count < amountToSpawn && attempts < maxAttempts)
        {
            attempts++;

            Vector3 spawnPosition = GetRandomTerrainPosition();

            if (IsValidPosition(spawnPosition))
            {
                GameObject obelisk = Instantiate(
                    obeliskPrefab,
                    spawnPosition,
                    Quaternion.identity
                );

                spawnedObelisks.Add(obelisk);

                if(enableDecay)
                {
                    StartCoroutine(DecayObelisk(obelisk));
                }
            }
        }
    }

    Vector3 GetRandomTerrainPosition()
    {
        Vector3 terrainSize = terrain.terrainData.size;

        float x = Random.Range(0, terrainSize.x);
        float z = Random.Range(0, terrainSize.z);

        float y = terrain.SampleHeight(
            new Vector3(x,0,z)
        );

        return terrain.transform.position +
        new Vector3(
            x,
            y + spawnHeightOffset,
            z
        );
    }

    bool IsValidPosition(Vector3 position)
    {
        foreach(GameObject obj in spawnedObelisks)
        {
            if(obj == null)
                continue;

            float distance = Vector3.Distance(
                position,
                obj.transform.position
            );

            if(distance < minDistanceBetweenObelisks)
                return false;
        }

        return true;
    }

    System.Collections.IEnumerator DecayObelisk(GameObject obelisk)
    {
        yield return new WaitForSeconds(decayTime);

        if(obelisk != null)
        {
            spawnedObelisks.Remove(obelisk);
            Destroy(obelisk);
        }

        yield return new WaitForSeconds(respawnTime);

        SpawnSingleObelisk();
    }

    void SpawnSingleObelisk()
    {
        int attempts = 0;

        while(attempts < 100)
        {
            attempts++;

            Vector3 pos = GetRandomTerrainPosition();

            if(IsValidPosition(pos))
            {
                GameObject obelisk = Instantiate(
                    obeliskPrefab,
                    pos,
                    Quaternion.identity
                );

                spawnedObelisks.Add(obelisk);

                if(enableDecay)
                    StartCoroutine(DecayObelisk(obelisk));

                break;
            }
        }
    }
}

end

房间

using UnityEngine;
using System.Collections;

public class ObeliskRoomInterior : MonoBehaviour
{
    [Header("玩家")]
    public Transform player;

    [Header("房间预制体")]
    public GameObject roomPrefab;

    [Header("房间设置")]
    public Transform outsideExitPoint;

    public float enterDistance = 4f;
    public float roomDepth = -500f;

    [Header("生成保护")]
    public float spawnHeightOffset = 1.5f;
    public float teleportDelay = 0.1f;

    [Header("地形边界")]
    public Terrain terrain;
    public float borderDistance = 30f;

    [Header("输入")]
    public KeyCode enterKey = KeyCode.E;
    public KeyCode exitKey = KeyCode.F;

    private GameObject spawnedRoom;
    private bool playerInside = false;

    void Update()
    {
        if (!playerInside)
        {
            float distance = Vector3.Distance(
                player.position,
                transform.position
            );

            if(distance <= enterDistance)
            {
                if(Input.GetKeyDown(enterKey))
                {
                    EnterRoom();
                }
            }
        }
        else
        {
            if(Input.GetKeyDown(exitKey))
            {
                ExitRoom();
            }
        }
    }

    void EnterRoom()
    {
        playerInside = true;

        Vector3 roomPosition = new Vector3(
            transform.position.x,
            roomDepth,
            transform.position.z
        );

        // 使房间远离地形边界
        if(terrain != null)
        {
            Vector3 terrainPos = terrain.transform.position;
            Vector3 terrainSize = terrain.terrainData.size;

            roomPosition.x = Mathf.Clamp(
                roomPosition.x,
                terrainPos.x + borderDistance,
                terrainPos.x + terrainSize.x - borderDistance
            );

            roomPosition.z = Mathf.Clamp(
                roomPosition.z,
                terrainPos.z + borderDistance,
                terrainPos.z + terrainSize.z - borderDistance
            );
        }

        spawnedRoom = Instantiate(
            roomPrefab,
            roomPosition,
            Quaternion.identity
        );

        ObeliskRoomExit exit =
            spawnedRoom.GetComponentInChildren<ObeliskRoomExit>();

        if(exit != null)
        {
            exit.owner = this;
            exit.player = player;
            exit.outsidePosition = outsideExitPoint.position;
        }

        Transform insideSpawn =
            spawnedRoom.transform.Find("InsideSpawn");

        if(insideSpawn != null)
        {
            StartCoroutine(TeleportInside(insideSpawn));
        }
        else
        {
            StartCoroutine(TeleportInside(spawnedRoom.transform));
        }
    }

    IEnumerator TeleportInside(Transform spawn)
    {
        // 等待房间碰撞体加载
        yield return new WaitForSeconds(teleportDelay);

        CharacterController controller =
            player.GetComponent<CharacterController>();

        if(controller != null)
        {
            controller.enabled = false;
        }

        player.position = spawn.position + Vector3.up * spawnHeightOffset;

        if(controller != null)
        {
            controller.enabled = true;
        }
    }

    public void ExitRoom()
    {
        playerInside = false;

        CharacterController controller =
            player.GetComponent<CharacterController>();

        if(controller != null)
        {
            controller.enabled = false;
        }

        player.position = outsideExitPoint.position;

        if(controller != null)
        {
            controller.enabled = true;
        }

        if(spawnedRoom != null)
        {
            Destroy(spawnedRoom);
        }
    }
}

end

房间出口

using UnityEngine;

public class ObeliskRoomExit : MonoBehaviour
{
    [HideInInspector]
    public ObeliskRoomInterior owner;

    [HideInInspector]
    public Transform player;

    public Vector3 outsidePosition;

    public KeyCode exitKey = KeyCode.F;

    void Update()
    {
        if(player == null)
            return;

        float distance =
            Vector3.Distance(
                player.position,
                transform.position
            );

        if(distance < 3f)
        {
            if(Input.GetKeyDown(exitKey))
            {
                Exit();
            }
        }
    }

    void Exit()
    {
        owner.ExitRoom();
    }
}

end

部队存储

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class SimpleObeliskTroopStorage : MonoBehaviour
{
    [Header("存储")]
    public int maxTroops = 20;
    public float storageRange = 5f;

    [Header("释放")]
    public float releaseDistance = 2f;

    [Header("控制")]
    public KeyCode storeKey = KeyCode.G;
    public KeyCode releaseKey = KeyCode.R;

    private List<GameObject> storedTroops = new List<GameObject>();

    void Update()
    {
        if(Input.GetKeyDown(storeKey))
        {
            StoreTroop();
        }

        if(Input.GetKeyDown(releaseKey))
        {
            ReleaseTroop();
        }
    }

    void StoreTroop()
    {
        if(storedTroops.Count >= maxTroops)
        {
            Debug.Log("方尖碑存储已满");
            return;
        }

        Collider[] nearby =
        Physics.OverlapSphere(
            transform.position,
            storageRange
        );

        foreach(Collider col in nearby)
        {
            TroopAI troop =
            col.GetComponentInParent<TroopAI>();

            if(troop != null)
            {
                GameObject troopObject =
                troop.gameObject;

                storedTroops.Add(troopObject);

                troopObject.SetActive(false);

                Debug.Log(
                "已存储部队: "
                + troopObject.name
                );

                return;
            }
        }

        Debug.Log("附近没有部队");
    }

    void ReleaseTroop()
    {
        if(storedTroops.Count == 0)
        {
            Debug.Log("没有已存储的部队");
            return;
        }

        GameObject troop =
        storedTroops[0];

        storedTroops.RemoveAt(0);

        Vector3 spawnPosition =
        transform.position +
        transform.forward * releaseDistance;

        troop.transform.position =
        spawnPosition;

        troop.transform.rotation =
        transform.rotation;

        troop.SetActive(true);

        NavMeshAgent agent =
        troop.GetComponent<NavMeshAgent>();

        if(agent != null)
        {
            agent.enabled = true;
            agent.Warp(spawnPosition);
        }

        Debug.Log(
        "已释放部队: "
        + troop.name
        );
    }

    public int GetStoredTroopCount()
    {
        return storedTroops.Count;
    }

    void OnDrawGizmosSelected()
    {
        Gizmos.DrawWireSphere(
            transform.position,
            storageRange
        );
    }
}

end