Nuevo funcionamiento TALANES
This commit is contained in:
1 parent
95a1231c63
commit
92daf86379
2 files changed
+602
-50
No files matched your search
+601
-41
@@ -1,10 +1,14 @@
|
||||
#include "raylib.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
extern Vector3 playerPosition;
|
||||
extern bool IsSolidAt(Vector3 position, float radius);
|
||||
|
||||
const int NPC_COUNT = 12;
|
||||
const int NPC_COUNT = 20;
|
||||
|
||||
Vector3 enemyPosition[NPC_COUNT] =
|
||||
{
|
||||
@@ -21,17 +25,349 @@ Vector3 enemyPosition[NPC_COUNT] =
|
||||
{ -28.0f, 1.0f, -68.0f },
|
||||
{ 28.0f, 1.0f, -68.0f },
|
||||
{ -28.0f, 1.0f, 68.0f },
|
||||
{ 28.0f, 1.0f, 68.0f }
|
||||
{ 28.0f, 1.0f, 68.0f },
|
||||
|
||||
{ -60.0f, 1.0f, -52.0f },
|
||||
{ 60.0f, 1.0f, -52.0f },
|
||||
{ -60.0f, 1.0f, 52.0f },
|
||||
{ 60.0f, 1.0f, 52.0f },
|
||||
|
||||
{ -16.0f, 1.0f, -48.0f },
|
||||
{ 16.0f, 1.0f, -48.0f },
|
||||
{ -16.0f, 1.0f, 48.0f },
|
||||
{ 16.0f, 1.0f, 48.0f }
|
||||
};
|
||||
|
||||
float enemyWalkTime[NPC_COUNT] = {};
|
||||
float enemyStunTime[NPC_COUNT] = {};
|
||||
float enemySpin[NPC_COUNT] = {};
|
||||
bool enemyMoving[NPC_COUNT] = {};
|
||||
|
||||
const float enemySpeed = 2.4f;
|
||||
const float enemySpeed = 5.5f;
|
||||
const float enemyStunDuration = 2.5f;
|
||||
|
||||
bool enemyMoving[NPC_COUNT] = {};
|
||||
static bool showEnemyPaths = false;
|
||||
|
||||
static const int GRID_SIZE = 41;
|
||||
static const float CELL_SIZE = 4.0f;
|
||||
static const float GRID_OFFSET = 20.0f;
|
||||
|
||||
struct GridNode
|
||||
{
|
||||
int x;
|
||||
int z;
|
||||
|
||||
bool operator==(const GridNode& other) const
|
||||
{
|
||||
return x == other.x && z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<GridNode> enemyPath[NPC_COUNT];
|
||||
|
||||
static int WorldToGridX(float x)
|
||||
{
|
||||
return (int)std::round(x / CELL_SIZE + GRID_OFFSET);
|
||||
}
|
||||
|
||||
static int WorldToGridZ(float z)
|
||||
{
|
||||
return (int)std::round(z / CELL_SIZE + GRID_OFFSET);
|
||||
}
|
||||
|
||||
static Vector3 GridToWorld(int x, int z)
|
||||
{
|
||||
return
|
||||
{
|
||||
(x - GRID_OFFSET) * CELL_SIZE,
|
||||
1.0f,
|
||||
(z - GRID_OFFSET) * CELL_SIZE
|
||||
};
|
||||
}
|
||||
|
||||
static bool IsInsideGrid(int x, int z)
|
||||
{
|
||||
return
|
||||
x >= 0 &&
|
||||
x < GRID_SIZE &&
|
||||
z >= 0 &&
|
||||
z < GRID_SIZE;
|
||||
}
|
||||
|
||||
static bool IsWalkableCell(int x, int z)
|
||||
{
|
||||
if (!IsInsideGrid(x, z))
|
||||
return false;
|
||||
|
||||
Vector3 position = GridToWorld(x, z);
|
||||
|
||||
return !IsSolidAt(position, 0.42f);
|
||||
}
|
||||
|
||||
static float GridDistance(
|
||||
int x1,
|
||||
int z1,
|
||||
int x2,
|
||||
int z2
|
||||
)
|
||||
{
|
||||
float dx = (float)(x1 - x2);
|
||||
float dz = (float)(z1 - z2);
|
||||
|
||||
return std::sqrt(dx * dx + dz * dz);
|
||||
}
|
||||
|
||||
static std::vector<GridNode> FindPath(
|
||||
Vector3 startWorld,
|
||||
Vector3 targetWorld
|
||||
)
|
||||
{
|
||||
GridNode start =
|
||||
{
|
||||
WorldToGridX(startWorld.x),
|
||||
WorldToGridZ(startWorld.z)
|
||||
};
|
||||
|
||||
GridNode target =
|
||||
{
|
||||
WorldToGridX(targetWorld.x),
|
||||
WorldToGridZ(targetWorld.z)
|
||||
};
|
||||
|
||||
if (!IsInsideGrid(start.x, start.z))
|
||||
return {};
|
||||
|
||||
if (!IsInsideGrid(target.x, target.z))
|
||||
return {};
|
||||
|
||||
// Si el destino está ocupado por un mueble,
|
||||
// buscamos la celda caminable más cercana.
|
||||
if (!IsWalkableCell(target.x, target.z))
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (int radius = 1; radius <= 5 && !found; radius++)
|
||||
{
|
||||
for (int z = target.z - radius;
|
||||
z <= target.z + radius && !found;
|
||||
z++)
|
||||
{
|
||||
for (int x = target.x - radius;
|
||||
x <= target.x + radius;
|
||||
x++)
|
||||
{
|
||||
if (IsWalkableCell(x, z))
|
||||
{
|
||||
target = {x, z};
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!IsWalkableCell(start.x, start.z))
|
||||
return {};
|
||||
|
||||
struct Node
|
||||
{
|
||||
int x;
|
||||
int z;
|
||||
float g;
|
||||
float f;
|
||||
};
|
||||
|
||||
std::vector<Node> open;
|
||||
std::vector<bool> closed(
|
||||
GRID_SIZE * GRID_SIZE,
|
||||
false
|
||||
);
|
||||
|
||||
std::vector<GridNode> cameFrom(
|
||||
GRID_SIZE * GRID_SIZE,
|
||||
{-1, -1}
|
||||
);
|
||||
|
||||
std::vector<float> gScore(
|
||||
GRID_SIZE * GRID_SIZE,
|
||||
std::numeric_limits<float>::infinity()
|
||||
);
|
||||
|
||||
auto indexOf =
|
||||
[](int x, int z)
|
||||
{
|
||||
return z * GRID_SIZE + x;
|
||||
};
|
||||
|
||||
gScore[indexOf(start.x, start.z)] = 0.0f;
|
||||
|
||||
open.push_back(
|
||||
{
|
||||
start.x,
|
||||
start.z,
|
||||
0.0f,
|
||||
GridDistance(
|
||||
start.x,
|
||||
start.z,
|
||||
target.x,
|
||||
target.z
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const int directions[4][2] =
|
||||
{
|
||||
{ 1, 0 },
|
||||
{-1, 0 },
|
||||
{ 0, 1 },
|
||||
{ 0,-1 }
|
||||
};
|
||||
|
||||
while (!open.empty())
|
||||
{
|
||||
int best = 0;
|
||||
|
||||
for (int i = 1; i < (int)open.size(); i++)
|
||||
{
|
||||
if (open[i].f < open[best].f)
|
||||
best = i;
|
||||
}
|
||||
|
||||
Node current = open[best];
|
||||
open.erase(open.begin() + best);
|
||||
|
||||
if (current.x == target.x &&
|
||||
current.z == target.z)
|
||||
{
|
||||
std::vector<GridNode> path;
|
||||
|
||||
GridNode currentNode =
|
||||
{
|
||||
target.x,
|
||||
target.z
|
||||
};
|
||||
|
||||
while (!(currentNode == start))
|
||||
{
|
||||
path.push_back(currentNode);
|
||||
|
||||
GridNode previous =
|
||||
cameFrom[
|
||||
indexOf(
|
||||
currentNode.x,
|
||||
currentNode.z
|
||||
)
|
||||
];
|
||||
|
||||
if (previous.x < 0)
|
||||
{
|
||||
path.clear();
|
||||
return path;
|
||||
}
|
||||
|
||||
currentNode = previous;
|
||||
}
|
||||
|
||||
path.push_back(start);
|
||||
|
||||
std::reverse(
|
||||
path.begin(),
|
||||
path.end()
|
||||
);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
int currentIndex =
|
||||
indexOf(current.x, current.z);
|
||||
|
||||
if (closed[currentIndex])
|
||||
continue;
|
||||
|
||||
closed[currentIndex] = true;
|
||||
|
||||
for (const auto& direction : directions)
|
||||
{
|
||||
int nx = current.x + direction[0];
|
||||
int nz = current.z + direction[1];
|
||||
|
||||
if (!IsInsideGrid(nx, nz))
|
||||
continue;
|
||||
|
||||
if (!IsWalkableCell(nx, nz))
|
||||
continue;
|
||||
|
||||
int nextIndex = indexOf(nx, nz);
|
||||
|
||||
if (closed[nextIndex])
|
||||
continue;
|
||||
|
||||
float newG = current.g + 1.0f;
|
||||
|
||||
if (newG < gScore[nextIndex])
|
||||
{
|
||||
gScore[nextIndex] = newG;
|
||||
|
||||
cameFrom[nextIndex] =
|
||||
{
|
||||
current.x,
|
||||
current.z
|
||||
};
|
||||
|
||||
float h =
|
||||
GridDistance(
|
||||
nx,
|
||||
nz,
|
||||
target.x,
|
||||
target.z
|
||||
);
|
||||
|
||||
open.push_back(
|
||||
{
|
||||
nx,
|
||||
nz,
|
||||
newG,
|
||||
newG + h
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool PositionIsFreeForEnemy(
|
||||
Vector3 position,
|
||||
int ignoreIndex
|
||||
)
|
||||
{
|
||||
if (IsSolidAt(position, 0.45f))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
{
|
||||
if (i == ignoreIndex)
|
||||
continue;
|
||||
|
||||
float dx =
|
||||
enemyPosition[i].x -
|
||||
position.x;
|
||||
|
||||
float dz =
|
||||
enemyPosition[i].z -
|
||||
position.z;
|
||||
|
||||
if (dx * dx + dz * dz < 0.75f * 0.75f)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MoveEnemyWithCollision(
|
||||
int index,
|
||||
@@ -39,23 +375,206 @@ void MoveEnemyWithCollision(
|
||||
float moveZ
|
||||
)
|
||||
{
|
||||
Vector3 nextX = enemyPosition[index];
|
||||
Vector3 nextX =
|
||||
enemyPosition[index];
|
||||
|
||||
nextX.x += moveX;
|
||||
|
||||
if (!IsSolidAt(nextX, 0.45f))
|
||||
if (PositionIsFreeForEnemy(nextX, index))
|
||||
enemyPosition[index].x = nextX.x;
|
||||
|
||||
Vector3 nextZ = enemyPosition[index];
|
||||
Vector3 nextZ =
|
||||
enemyPosition[index];
|
||||
|
||||
nextZ.z += moveZ;
|
||||
|
||||
if (!IsSolidAt(nextZ, 0.45f))
|
||||
if (PositionIsFreeForEnemy(nextZ, index))
|
||||
enemyPosition[index].z = nextZ.z;
|
||||
}
|
||||
|
||||
static void RecalculatePath(int index)
|
||||
{
|
||||
enemyPath[index] =
|
||||
FindPath(
|
||||
enemyPosition[index],
|
||||
playerPosition
|
||||
);
|
||||
}
|
||||
|
||||
static void MoveEnemyAlongPath(
|
||||
int index,
|
||||
float deltaTime
|
||||
)
|
||||
{
|
||||
if (enemyPath[index].size() < 2)
|
||||
{
|
||||
enemyMoving[index] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 target =
|
||||
GridToWorld(
|
||||
enemyPath[index][1].x,
|
||||
enemyPath[index][1].z
|
||||
);
|
||||
|
||||
float dx =
|
||||
target.x -
|
||||
enemyPosition[index].x;
|
||||
|
||||
float dz =
|
||||
target.z -
|
||||
enemyPosition[index].z;
|
||||
|
||||
float distance =
|
||||
std::sqrt(dx * dx + dz * dz);
|
||||
|
||||
if (distance < 0.18f)
|
||||
{
|
||||
enemyPath[index].erase(
|
||||
enemyPath[index].begin()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dx /= distance;
|
||||
dz /= distance;
|
||||
|
||||
MoveEnemyWithCollision(
|
||||
index,
|
||||
dx * enemySpeed * deltaTime,
|
||||
dz * enemySpeed * deltaTime
|
||||
);
|
||||
|
||||
enemyMoving[index] = true;
|
||||
enemyWalkTime[index] += deltaTime * 9.0f;
|
||||
}
|
||||
|
||||
static int FindClosestEnemy()
|
||||
{
|
||||
int best = 0;
|
||||
float bestDistance =
|
||||
std::numeric_limits<float>::infinity();
|
||||
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
{
|
||||
float dx =
|
||||
enemyPosition[i].x -
|
||||
playerPosition.x;
|
||||
|
||||
float dz =
|
||||
enemyPosition[i].z -
|
||||
playerPosition.z;
|
||||
|
||||
float distance =
|
||||
dx * dx + dz * dz;
|
||||
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestDistance = distance;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
static bool HasNearbyTalan()
|
||||
{
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
{
|
||||
float dx =
|
||||
enemyPosition[i].x -
|
||||
playerPosition.x;
|
||||
|
||||
float dz =
|
||||
enemyPosition[i].z -
|
||||
playerPosition.z;
|
||||
|
||||
float distance =
|
||||
std::sqrt(dx * dx + dz * dz);
|
||||
|
||||
if (distance < 28.0f)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void TeleportOneTalanToPlayerArea()
|
||||
{
|
||||
int index = FindClosestEnemy();
|
||||
|
||||
float bestDistance = 18.0f;
|
||||
|
||||
for (int attempt = 0; attempt < 40; attempt++)
|
||||
{
|
||||
float angle =
|
||||
(float)attempt *
|
||||
(PI * 2.0f / 40.0f);
|
||||
|
||||
float distance =
|
||||
bestDistance +
|
||||
(attempt % 5) * 1.5f;
|
||||
|
||||
Vector3 position =
|
||||
{
|
||||
playerPosition.x +
|
||||
std::cos(angle) * distance,
|
||||
|
||||
1.0f,
|
||||
|
||||
playerPosition.z +
|
||||
std::sin(angle) * distance
|
||||
};
|
||||
|
||||
if (!PositionIsFreeForEnemy(
|
||||
position,
|
||||
index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<GridNode> testPath =
|
||||
FindPath(
|
||||
position,
|
||||
playerPosition
|
||||
);
|
||||
|
||||
if (!testPath.empty())
|
||||
{
|
||||
enemyPosition[index] =
|
||||
position;
|
||||
|
||||
enemyWalkTime[index] = 0.0f;
|
||||
enemyStunTime[index] = 0.0f;
|
||||
enemySpin[index] = 0.0f;
|
||||
|
||||
enemyPath[index] =
|
||||
testPath;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateEnemy(float deltaTime)
|
||||
{
|
||||
// P muestra/oculta las rutas.
|
||||
if (IsKeyPressed(KEY_P))
|
||||
{
|
||||
showEnemyPaths =
|
||||
!showEnemyPaths;
|
||||
}
|
||||
|
||||
// Siempre debe haber un talán relativamente
|
||||
// cerca del jugador.
|
||||
if (!HasNearbyTalan())
|
||||
{
|
||||
TeleportOneTalanToPlayerArea();
|
||||
}
|
||||
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
{
|
||||
float dx =
|
||||
@@ -67,10 +586,7 @@ void UpdateEnemy(float deltaTime)
|
||||
enemyPosition[i].z;
|
||||
|
||||
float distance =
|
||||
std::sqrt(
|
||||
dx * dx +
|
||||
dz * dz
|
||||
);
|
||||
std::sqrt(dx * dx + dz * dz);
|
||||
|
||||
// =========================================
|
||||
// MAREADO
|
||||
@@ -116,24 +632,23 @@ void UpdateEnemy(float deltaTime)
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// SEGUIR JUGADOR
|
||||
// RUTA INTELIGENTE
|
||||
// =========================================
|
||||
|
||||
// Recalcular frecuentemente para que los talanes
|
||||
// reaccionen si el jugador cambia de dirección.
|
||||
if (enemyPath[i].empty() ||
|
||||
((int)(GetTime() * 4.0) + i) % 5 == 0)
|
||||
{
|
||||
RecalculatePath(i);
|
||||
}
|
||||
|
||||
if (distance > 1.8f)
|
||||
{
|
||||
dx /= distance;
|
||||
dz /= distance;
|
||||
|
||||
MoveEnemyWithCollision(
|
||||
MoveEnemyAlongPath(
|
||||
i,
|
||||
dx * enemySpeed * deltaTime,
|
||||
dz * enemySpeed * deltaTime
|
||||
deltaTime
|
||||
);
|
||||
|
||||
enemyWalkTime[i] +=
|
||||
deltaTime * 9.0f;
|
||||
|
||||
enemyMoving[i] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -195,10 +710,6 @@ void TeleportEnemyNearPlayer(
|
||||
index >= NPC_COUNT)
|
||||
return;
|
||||
|
||||
// =========================================
|
||||
// CONSERVAR LA DISTANCIA ACTUAL
|
||||
// =========================================
|
||||
|
||||
float dx =
|
||||
enemyPosition[index].x -
|
||||
playerPosition.x;
|
||||
@@ -216,10 +727,6 @@ void TeleportEnemyNearPlayer(
|
||||
if (oldDistance < 3.0f)
|
||||
oldDistance = 3.0f;
|
||||
|
||||
// =========================================
|
||||
// BUSCAR OTRA DIRECCIÓN
|
||||
// =========================================
|
||||
|
||||
float baseAngle =
|
||||
std::atan2(
|
||||
dz,
|
||||
@@ -244,17 +751,23 @@ void TeleportEnemyNearPlayer(
|
||||
std::sin(angle) * oldDistance
|
||||
};
|
||||
|
||||
if (!IsSolidAt(newPosition, 0.45f))
|
||||
if (!PositionIsFreeForEnemy(
|
||||
newPosition,
|
||||
index))
|
||||
{
|
||||
enemyPosition[index] =
|
||||
newPosition;
|
||||
|
||||
enemyStunTime[index] = 0.0f;
|
||||
enemySpin[index] = 0.0f;
|
||||
enemyWalkTime[index] = 0.0f;
|
||||
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
enemyPosition[index] =
|
||||
newPosition;
|
||||
|
||||
enemyStunTime[index] = 0.0f;
|
||||
enemySpin[index] = 0.0f;
|
||||
enemyWalkTime[index] = 0.0f;
|
||||
|
||||
RecalculatePath(index);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +801,53 @@ int CheckEnemyHit(Vector3 bulletPosition)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// RUTAS VISIBLES CON P
|
||||
// =============================================
|
||||
|
||||
void DrawEnemyPaths()
|
||||
{
|
||||
if (!showEnemyPaths)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
{
|
||||
if (enemyPath[i].size() < 2)
|
||||
continue;
|
||||
|
||||
Vector3 previous =
|
||||
enemyPosition[i];
|
||||
|
||||
previous.y = 0.12f;
|
||||
|
||||
for (size_t p = 1;
|
||||
p < enemyPath[i].size();
|
||||
p++)
|
||||
{
|
||||
Vector3 current =
|
||||
GridToWorld(
|
||||
enemyPath[i][p].x,
|
||||
enemyPath[i][p].z
|
||||
);
|
||||
|
||||
current.y = 0.12f;
|
||||
|
||||
DrawLine3D(
|
||||
previous,
|
||||
current,
|
||||
LIME
|
||||
);
|
||||
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// TALÁN ORIGINAL
|
||||
// NO TOCAR
|
||||
// =============================================
|
||||
|
||||
void DrawEnemy()
|
||||
{
|
||||
for (int i = 0; i < NPC_COUNT; i++)
|
||||
|
||||
@@ -12,7 +12,7 @@ float playerPitch = 0.0f;
|
||||
float weaponBobTime = 0.0f;
|
||||
float weaponShootTime = 0.0f;
|
||||
|
||||
const float moveSpeed = 7.0f;
|
||||
const float moveSpeed = 11.0f;
|
||||
const float jumpForce = 9.0f;
|
||||
const float gravity = 20.0f;
|
||||
|
||||
@@ -50,22 +50,17 @@ Camera3D GetPlayerCamera()
|
||||
|
||||
static void MovePlayer(Vector3 movement)
|
||||
{
|
||||
// X separado de Z para poder deslizarse por las paredes.
|
||||
Vector3 nextX = playerPosition;
|
||||
nextX.x += movement.x;
|
||||
|
||||
if (!IsSolidAt(nextX, PLAYER_RADIUS))
|
||||
{
|
||||
playerPosition.x = nextX.x;
|
||||
}
|
||||
|
||||
Vector3 nextZ = playerPosition;
|
||||
nextZ.z += movement.z;
|
||||
|
||||
if (!IsSolidAt(nextZ, PLAYER_RADIUS))
|
||||
{
|
||||
playerPosition.z = nextZ.z;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePlayer(float deltaTime)
|
||||
@@ -150,9 +145,7 @@ void UpdatePlayer(float deltaTime)
|
||||
verticalVelocity = 0.0f;
|
||||
|
||||
if (IsKeyPressed(KEY_SPACE))
|
||||
{
|
||||
verticalVelocity = jumpForce;
|
||||
}
|
||||
}
|
||||
|
||||
verticalVelocity -= gravity * deltaTime;
|
||||
@@ -166,7 +159,6 @@ void UpdatePlayer(float deltaTime)
|
||||
verticalVelocity = 0.0f;
|
||||
}
|
||||
|
||||
// Colisión con el techo.
|
||||
if (playerPosition.y + PLAYER_HEAD_Y > CEILING_Y)
|
||||
{
|
||||
playerPosition.y =
|
||||
|
||||
Reference in new issue
Block a user