220 lines
9.1 KiB
C#
220 lines
9.1 KiB
C#
using UnityEngine;
|
|||
|
|
|
||
|
|
namespace Ironspore
|
||
|
|
{
|
||
|
|
public sealed class MechPilot : MonoBehaviour
|
||
|
|
{
|
||
|
|
public float Health
|
||
|
|
{
|
||
|
|
get; private set;
|
||
|
|
}
|
||
|
|
public float Energy
|
||
|
|
{
|
||
|
|
get; private set;
|
||
|
|
}
|
||
|
|
public float HorizontalSpeed
|
||
|
|
{
|
||
|
|
get; private set;
|
||
|
|
}
|
||
|
|
public int LastDamageFrame { get; private set; } = -1;
|
||
|
|
public bool FirstPerson
|
||
|
|
{
|
||
|
|
get; private set;
|
||
|
|
}
|
||
|
|
public bool Grounded => motor && motor.isGrounded;
|
||
|
|
public float Guard
|
||
|
|
{
|
||
|
|
get; private set;
|
||
|
|
}
|
||
|
|
GameSession game;
|
||
|
|
CharacterController motor;
|
||
|
|
Camera view;
|
||
|
|
Transform visual;
|
||
|
|
GameObject cockpit;
|
||
|
|
float yaw, pitch, vertical, boostRemaining, boostCooldown, shotCooldown, sideCooldown;
|
||
|
|
Vector3 boostDirection;
|
||
|
|
bool freshTrigger = true;
|
||
|
|
float lastHitTime;
|
||
|
|
|
||
|
|
public void Initialize(GameSession session, GameObject model)
|
||
|
|
{
|
||
|
|
game = session;
|
||
|
|
motor = gameObject.AddComponent<CharacterController>();
|
||
|
|
motor.height = 3.15f;
|
||
|
|
motor.radius = .65f;
|
||
|
|
motor.center = new Vector3(0, 1.6f, 0);
|
||
|
|
motor.stepOffset = .45f;
|
||
|
|
motor.slopeLimit = 55;
|
||
|
|
gameObject.layer = 8;
|
||
|
|
visual = new GameObject("MechVisual").transform;
|
||
|
|
visual.SetParent(transform, false);
|
||
|
|
var modelInstance = Instantiate(model, visual);
|
||
|
|
modelInstance.transform.localScale = Vector3.one;
|
||
|
|
visual.localPosition = Vector3.zero;
|
||
|
|
foreach (var t in visual.GetComponentsInChildren<Transform>())
|
||
|
|
t.gameObject.layer = 8;
|
||
|
|
var cam = new GameObject("PilotCamera");
|
||
|
|
cam.transform.SetParent(transform);
|
||
|
|
view = cam.AddComponent<Camera>();
|
||
|
|
view.nearClipPlane = .08f;
|
||
|
|
view.farClipPlane = 250;
|
||
|
|
view.fieldOfView = 70;
|
||
|
|
cam.AddComponent<AudioListener>();
|
||
|
|
cockpit = WorldFactory.Cockpit(view.transform);
|
||
|
|
Health = game.Run.armor;
|
||
|
|
Energy = game.Run.energy;
|
||
|
|
ApplyLoadout();
|
||
|
|
}
|
||
|
|
public void ApplyLoadout()
|
||
|
|
{
|
||
|
|
visual.localScale = game.chassis[game.Run.chassis].modelScale;
|
||
|
|
}
|
||
|
|
public void RequireFreshTrigger()
|
||
|
|
{
|
||
|
|
freshTrigger = true;
|
||
|
|
}
|
||
|
|
void Update()
|
||
|
|
{
|
||
|
|
if (!game.InField || game.Paused)
|
||
|
|
return;
|
||
|
|
var frame = game.chassis[game.Run.chassis];
|
||
|
|
float dt = Time.deltaTime;
|
||
|
|
boostCooldown -= dt;
|
||
|
|
shotCooldown -= dt;
|
||
|
|
sideCooldown -= dt;
|
||
|
|
Guard = Mathf.Max(0, Guard - dt);
|
||
|
|
Vector2 movement = game.MenuOpen ? Vector2.zero : game.Action("Move").ReadValue<Vector2>();
|
||
|
|
bool usingEnergy = false;
|
||
|
|
if (!game.MenuOpen)
|
||
|
|
{
|
||
|
|
Vector2 look = game.Action("Look").ReadValue<Vector2>();
|
||
|
|
yaw += look.x * .105f;
|
||
|
|
pitch = Mathf.Clamp(pitch - look.y * .105f, -72, 75);
|
||
|
|
if (game.Action("View").WasPressedThisFrame())
|
||
|
|
FirstPerson = !FirstPerson;
|
||
|
|
if (game.Action("Guard").WasPressedThisFrame() && Energy >= 20)
|
||
|
|
{
|
||
|
|
Energy -= 20;
|
||
|
|
Guard = 2.5f;
|
||
|
|
}
|
||
|
|
if (game.Action("Boost").WasPressedThisFrame() && boostCooldown <= 0 && Energy >= 18)
|
||
|
|
{
|
||
|
|
boostDirection = Quaternion.Euler(0, yaw, 0) * new Vector3(movement.x, 0, movement.y == 0 && movement.x == 0 ? 1 : movement.y);
|
||
|
|
boostDirection.Normalize();
|
||
|
|
boostRemaining = frame.boostDuration;
|
||
|
|
boostCooldown = .65f;
|
||
|
|
Energy -= 18;
|
||
|
|
GameAudio.Cue(170, .1f);
|
||
|
|
}
|
||
|
|
if (motor.isGrounded && game.Action("Jump").WasPressedThisFrame())
|
||
|
|
vertical = 7;
|
||
|
|
if (frame.lift > 0 && game.Action("Jump").IsPressed() && Energy > 0 && !motor.isGrounded)
|
||
|
|
{
|
||
|
|
vertical = Mathf.MoveTowards(vertical, frame.lift, dt * 18);
|
||
|
|
Energy = Mathf.Max(0, Energy - dt * 23);
|
||
|
|
usingEnergy = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Vector3 velocity = Quaternion.Euler(0, yaw, 0) * new Vector3(movement.x, 0, movement.y);
|
||
|
|
float speed = frame.speed * game.Part(2, game.Run.equipment[2]).speedFactor;
|
||
|
|
velocity = Vector3.ClampMagnitude(velocity, 1) * speed;
|
||
|
|
if (boostRemaining > 0)
|
||
|
|
{
|
||
|
|
boostRemaining -= dt;
|
||
|
|
velocity = boostDirection * frame.boostSpeed;
|
||
|
|
usingEnergy = true;
|
||
|
|
}
|
||
|
|
if (motor.isGrounded && vertical < 0)
|
||
|
|
vertical = -2;
|
||
|
|
vertical -= dt * 20;
|
||
|
|
velocity.y = vertical;
|
||
|
|
motor.Move(velocity * dt);
|
||
|
|
HorizontalSpeed = new Vector2(motor.velocity.x, motor.velocity.z).magnitude;
|
||
|
|
visual.localRotation = Quaternion.Euler(0, Mathf.LerpAngle(visual.localEulerAngles.y, yaw, dt * 9), 0);
|
||
|
|
if (!usingEnergy && Guard <= 0)
|
||
|
|
Energy = Mathf.Min(100, Energy + dt * 18);
|
||
|
|
if (transform.position.y < -20)
|
||
|
|
Damage(1000);
|
||
|
|
if (!game.MenuOpen)
|
||
|
|
{
|
||
|
|
if (!game.Action("Fire").IsPressed())
|
||
|
|
freshTrigger = false;
|
||
|
|
if (!freshTrigger && game.Action("Fire").IsPressed() && shotCooldown <= 0)
|
||
|
|
Shoot(false);
|
||
|
|
if (game.Action("Secondary").WasPressedThisFrame() && sideCooldown <= 0 && Energy >= 15)
|
||
|
|
{
|
||
|
|
Energy -= 15;
|
||
|
|
Shoot(true);
|
||
|
|
sideCooldown = 2.5f;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void LateUpdate()
|
||
|
|
{
|
||
|
|
if (!view)
|
||
|
|
return;
|
||
|
|
Quaternion rotation = Quaternion.Euler(pitch, yaw, 0);
|
||
|
|
Vector3 pivot = transform.position + Vector3.up * 2.7f;
|
||
|
|
Vector3 desired = FirstPerson ? pivot + rotation * new Vector3(0, 0, .25f) : pivot + rotation * new Vector3(1.25f, .25f, -6);
|
||
|
|
if (!FirstPerson)
|
||
|
|
{
|
||
|
|
Vector3 offset = desired - pivot;
|
||
|
|
if (Physics.SphereCast(pivot, .25f, offset.normalized, out var obstruction, offset.magnitude, ~(1 << 8), QueryTriggerInteraction.Ignore))
|
||
|
|
desired = pivot + offset.normalized * Mathf.Max(.5f, obstruction.distance - .15f);
|
||
|
|
}
|
||
|
|
view.transform.SetPositionAndRotation(desired, rotation);
|
||
|
|
float fov = game.MenuOpen ? 70 : game.Action("Aim").IsPressed() ? 50 : FirstPerson ? 78 : 70;
|
||
|
|
view.fieldOfView = Mathf.Lerp(view.fieldOfView, fov, Time.unscaledDeltaTime * 12);
|
||
|
|
visual.gameObject.SetActive(!FirstPerson);
|
||
|
|
cockpit.SetActive(FirstPerson);
|
||
|
|
}
|
||
|
|
void Shoot(bool secondary)
|
||
|
|
{
|
||
|
|
var part = game.Part(secondary ? 1 : 0, game.Run.equipment[secondary ? 1 : 0]);
|
||
|
|
shotCooldown = secondary ? shotCooldown : 1 / part.fireRate;
|
||
|
|
Ray ray = view.ViewportPointToRay(new Vector3(.5f, .5f));
|
||
|
|
Vector3 target = ray.GetPoint(180);
|
||
|
|
if (Physics.Raycast(ray, out var aim, 180, ~(1 << 8), QueryTriggerInteraction.Ignore))
|
||
|
|
target = aim.point;
|
||
|
|
Vector3 muzzle = transform.position + Vector3.up * 2 + Quaternion.Euler(0, yaw, 0) * new Vector3(.85f, 0, 1);
|
||
|
|
if (secondary && part.guided)
|
||
|
|
{
|
||
|
|
var enemies = FindObjectsByType<EnemyUnit>();
|
||
|
|
float nearest = 50;
|
||
|
|
foreach (var enemy in enemies)
|
||
|
|
{
|
||
|
|
float d = Vector3.Distance(muzzle, enemy.transform.position);
|
||
|
|
if (d < nearest)
|
||
|
|
{
|
||
|
|
nearest = d;
|
||
|
|
target = enemy.transform.position + Vector3.up;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Vector3 direction = (target - muzzle).normalized;
|
||
|
|
if (Physics.Raycast(muzzle, direction, out var hit, 180, ~(1 << 8), QueryTriggerInteraction.Ignore))
|
||
|
|
{
|
||
|
|
target = hit.point;
|
||
|
|
var enemy = hit.collider.GetComponentInParent<EnemyUnit>();
|
||
|
|
if (enemy)
|
||
|
|
{
|
||
|
|
enemy.Damage(part.damage * (1 + game.Run.upgrades * .15f));
|
||
|
|
lastHitTime = Time.time;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
WorldFactory.Tracer(muzzle, target, secondary ? new Color(1, .65f, .2f) : new Color(.2f, 1, .85f));
|
||
|
|
GameAudio.Cue(secondary ? 140 : 240, .06f, .07f);
|
||
|
|
}
|
||
|
|
public bool RecentHit => Time.time - lastHitTime < .15f;
|
||
|
|
public void Damage(float amount)
|
||
|
|
{
|
||
|
|
if (!game.InField || Health <= 0)
|
||
|
|
return;
|
||
|
|
LastDamageFrame = Time.frameCount;
|
||
|
|
float armorFactor = game.Part(3, game.Run.equipment[3]).id == "reactive_guard" ? .72f : 1;
|
||
|
|
Health = Mathf.Max(0, Health - amount * armorFactor * (Guard > 0 ? .2f : 1));
|
||
|
|
game.Run.armor = Health;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|