working on charging and quick attacks

This commit is contained in:
2025-07-31 09:04:16 +02:00
parent 407454555f
commit a0c2a389be
34 changed files with 7683 additions and 289 deletions

View File

@@ -1,5 +1,6 @@
using System;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Simulation.Sensors;
using RebootReality.jelycho.Main;
using Unity.Cinemachine;
@@ -10,7 +11,7 @@ namespace RebootReality.jelycho.Player {
[AddComponentMenu(GameConsts.k_AddComponentMenu + "Player/First Person Camera")]
public class FPPCamera : MonoBehaviour {
[ConfigVar("fpp.camera.fov", 60.0f, "Field of view for the first person camera.")]
static ConfigVar s_cameraFOV;
static ConfigVar s_CameraFOV;
[Header("Base")]
[field: SerializeField]
@@ -22,10 +23,17 @@ namespace RebootReality.jelycho.Player {
[field: SerializeField]
public CinemachineCamera Camera { get; private set; }
CinemachineBasicMultiChannelPerlin m_BobbingNoiseComponent;
CinemachineBasicMultiChannelPerlin m_NoiseComponent;
[SerializeField] SignalSourceAsset m_BobbingNoiseSettings;
[SerializeField] float m_BobbingFrequency = 0.5f;
[SerializeField] float m_BobbingAmplitude = 0.75f;
float m_BobbingIntensity = 0.0f;
[SerializeField] SignalSourceAsset m_ShakeNoiseSettings;
[SerializeField] float m_ShakeFrequency = 1.0f;
[SerializeField] float m_ShakeAmplitude = 1.0f;
[Header("Picking")]
[SerializeField] float m_PickDistance = 5.0f;
@@ -46,16 +54,30 @@ namespace RebootReality.jelycho.Player {
}
void Awake() {
m_BobbingNoiseComponent =
m_NoiseComponent =
Camera.GetCinemachineComponent(CinemachineCore.Stage.Noise) as CinemachineBasicMultiChannelPerlin;
Assert.IsNotNull(m_BobbingNoiseComponent);
Assert.IsNotNull(m_NoiseComponent);
SetBobbing(0.0f);
}
public void Tick() {
Camera.Lens.FieldOfView = s_cameraFOV.FloatValue;
Camera.Lens.FieldOfView = s_CameraFOV.FloatValue;
// Camera.transform.localRotation = Quaternion.Euler(Pitch, 0f, 0f);
if (RR.World.Context is WorldContext worldContext) {
float shakeIntensity = worldContext.FeedbacksManager.GetShakeIntensityForPosition(transform.position);
if (shakeIntensity > 0.0f) {
m_NoiseComponent.NoiseProfile = m_ShakeNoiseSettings as NoiseSettings;
m_NoiseComponent.AmplitudeGain = m_ShakeAmplitude * shakeIntensity;
m_NoiseComponent.FrequencyGain = m_ShakeFrequency * shakeIntensity;
} else {
m_NoiseComponent.NoiseProfile = m_BobbingNoiseSettings as NoiseSettings;
m_NoiseComponent.AmplitudeGain = m_BobbingAmplitude * m_BobbingIntensity;
m_NoiseComponent.FrequencyGain = m_BobbingFrequency * m_BobbingIntensity;
}
}
}
public void Rotate(float x, float y) {
@@ -73,8 +95,7 @@ namespace RebootReality.jelycho.Player {
}
public void SetBobbing(float t) {
m_BobbingNoiseComponent.AmplitudeGain = m_BobbingAmplitude * t;
m_BobbingNoiseComponent.FrequencyGain = m_BobbingFrequency * t;
m_BobbingIntensity = t;
}
}
}

View File

@@ -5,6 +5,7 @@ using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Network;
using RebootKit.Engine.Simulation;
using RebootReality.jelycho.Enemies;
using RebootReality.jelycho.Items;
using Unity.Collections;
using Unity.Mathematics;
@@ -12,16 +13,6 @@ using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootReality.jelycho.Player {
[Serializable]
struct PlayerItemTypeHandsAnimationsConfig {
public ItemType itemType;
// @TODO: Cache hashes for state names
public string idle;
public string charging;
public string chargedUse;
}
public class PlayerActor : Actor {
static readonly Logger s_Logger = new Logger(nameof(PlayerActor));
@@ -62,15 +53,8 @@ namespace RebootReality.jelycho.Player {
[Header("Animations")]
[SerializeField] int m_HandsLayerIndex;
[SerializeField] PlayerItemTypeHandsAnimationsConfig[] m_ItemTypeHandsAnimations;
[SerializeField] PlayerItemTypeHandsAnimationsConfig m_DefaultItemHandsAnimations;
[SerializeField] string m_HandsIdleStateName = "Hands Locomotion";
[Header("Dragging")]
[SerializeField] Transform m_DragGutStartPosition;
[SerializeField] PhysicsObjectDragger m_PhysicsDragger;
[SerializeField] FloatRange m_DragDistanceRange = new FloatRange(1.0f, 5.0f);
[Header("Beacon location picking")]
[SerializeField] LayerMask m_BeaconPlacementLayerMask = 0;
[SerializeField] float m_BeaconPlacementMaxDistance = 15.0f;
@@ -95,9 +79,14 @@ namespace RebootReality.jelycho.Player {
ItemActor m_EquippedItem;
[SerializeField] float m_StartChargeDelay = 0.15f;
bool m_IsCharging;
float m_ChargeTimer;
int m_QuickAttackComboCounter;
float m_QuickAttackComboTimer;
public float3 LookDirection {
get {
float pitchRad = math.radians(-m_Camera.Pitch);
@@ -178,27 +167,6 @@ namespace RebootReality.jelycho.Player {
m_Locomotion.SetWishDirection(direction);
}
public void StartDrag() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot start dragging when not set up as owner.");
return;
}
GameObject pickedGameObject = m_Camera.Sensor.Sense();
if (pickedGameObject != null && pickedGameObject.TryGetComponent(out Rigidbody rigidbody)) {
m_PhysicsDragger.Grab(rigidbody);
}
}
public void StopDrag() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot stop dragging when not set up as owner.");
return;
}
m_PhysicsDragger.Drop();
}
public void DropItem() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot drop item when not set up as owner.");
@@ -213,42 +181,6 @@ namespace RebootReality.jelycho.Player {
}
}
PlayerItemTypeHandsAnimationsConfig GetHandsAnimationsConfig(ItemType itemType) {
foreach (PlayerItemTypeHandsAnimationsConfig config in m_ItemTypeHandsAnimations) {
if (config.itemType == itemType) {
return config;
}
}
return m_DefaultItemHandsAnimations;
}
void SetHandsIdleAnimation() {
if (m_EquippedItem != null) {
PlayerItemTypeHandsAnimationsConfig animationsConfig =
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
m_Animator.CrossFade(animationsConfig.idle, 0.0f, m_HandsLayerIndex);
} else {
m_Animator.CrossFade(m_HandsIdleStateName, 0.0f, m_HandsLayerIndex);
}
}
void SetChargingAnimation() {
if (m_EquippedItem != null) {
PlayerItemTypeHandsAnimationsConfig animationsConfig =
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
m_Animator.CrossFade(animationsConfig.charging, 0.0f, m_HandsLayerIndex);
}
}
void SetChargedUseAnimation() {
if (m_EquippedItem != null) {
PlayerItemTypeHandsAnimationsConfig animationsConfig =
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
m_Animator.CrossFade(animationsConfig.chargedUse, 0.0f, m_HandsLayerIndex);
}
}
public void BeginPrimaryAction() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot begin primary action when not set up as owner.");
@@ -259,13 +191,42 @@ namespace RebootReality.jelycho.Player {
return;
}
if (m_EquippedItem.Config.chargeAction != null && m_EquippedItem.Config.isChargeable) {
if (m_EquippedItem.Config.chargeAction.OnChargeStart(this, m_EquippedItem)) {
m_IsCharging = true;
m_ChargeTimer = 0.0f;
SetChargingAnimation();
m_IsCharging = false;
m_ChargeTimer = 0.0f;
if (m_QuickAttackComboTimer <= 0.0f) {
m_QuickAttackComboCounter = 0;
}
}
public void HoldingPrimaryAction() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot begin primary action when not set up as owner.");
return;
}
if (m_EquippedItem == null) {
return;
}
ItemConfig itemConfig = m_EquippedItem.Config;
if (!m_IsCharging && itemConfig.isChargeable && m_EquippedItem.Config.chargeAction != null ) {
m_ChargeTimer += Time.deltaTime;
if (m_ChargeTimer >= m_StartChargeDelay) {
if (itemConfig.chargeAction.OnChargeStart(this, m_EquippedItem)) {
SetChargingAnimation();
m_IsCharging = true;
m_ChargeTimer = 0.0f;
}
}
}
if (m_IsCharging) {
m_ChargeTimer += Time.deltaTime;
itemConfig.chargeAction.OnChargeUpdate(this, m_EquippedItem, GetChargeProgress());
}
}
float GetChargeProgress() {
@@ -285,6 +246,10 @@ namespace RebootReality.jelycho.Player {
return;
}
if (m_EquippedItem == null) {
return;
}
if (m_IsCharging) {
ItemConfig itemConfig = m_EquippedItem.Config;
@@ -298,6 +263,14 @@ namespace RebootReality.jelycho.Player {
}
m_IsCharging = false;
} else if (m_EquippedItem.Config.canQuickAttack) {
PlayQuickAttackAnimation(m_QuickAttackComboCounter);
m_QuickAttackComboCounter += 1;
m_QuickAttackComboTimer = 2.0f;
if (m_EquippedItem.Config.quickAttackAction != null) {
m_EquippedItem.Config.quickAttackAction.Attack(this, m_EquippedItem);
}
}
}
@@ -326,9 +299,68 @@ namespace RebootReality.jelycho.Player {
Pickup(itemActor);
} else if (m_TargetInteractable.Value is not null) {
m_TargetInteractable.Value.Interact();
// SetAnimatorTriggerRpc(AnimatorParamHashes.Throw);
}
}
//
// @MARK: Hands animations
//
void PlayHandsAnimation(string animationName) {
int hash = Animator.StringToHash(animationName);
if (!m_Animator.HasState(m_HandsLayerIndex, hash)) {
s_Logger.Error($"Animator does not have state with name {animationName}");
return;
}
PlayHandsAnimation(hash);
}
void PlayHandsAnimation(int animationHash) {
m_Animator.CrossFade(animationHash, 0.0f, m_HandsLayerIndex);
if (RR.IsServer()) {
PlayerPlayHandsAnimationEvent handsAnimationEvent = new PlayerPlayHandsAnimationEvent {
AnimationHash = animationHash
};
SendActorEvent((byte)PlayerActorEvents.PlayHandsAnimation, ref handsAnimationEvent);
} else {
PlayerActorRequestHandsAnimationCommand handsAnimationCommand =
new PlayerActorRequestHandsAnimationCommand {
AnimationHash = animationHash
};
SendActorCommand((byte) PlayerActorCommands.RequestHandsAnimation, ref handsAnimationCommand);
}
}
void SetHandsIdleAnimation() {
if (m_EquippedItem != null) {
PlayHandsAnimation(m_EquippedItem.Config.idleAnimation);
} else {
PlayHandsAnimation(m_HandsIdleStateName);
}
}
void SetChargingAnimation() {
if (m_EquippedItem != null) {
PlayHandsAnimation(m_EquippedItem.Config.chargingAnimation);
}
}
void SetChargedUseAnimation() {
if (m_EquippedItem != null) {
PlayHandsAnimation(m_EquippedItem.Config.chargedUseAnimation);
}
}
void PlayQuickAttackAnimation(int combo) {
if (m_EquippedItem == null || m_EquippedItem.Config.quickAttacksAnimations.Length == 0) {
return;
}
string animationName = m_EquippedItem.Config.quickAttacksAnimations[combo % m_EquippedItem.Config.quickAttacksAnimations.Length];
PlayHandsAnimation(animationName);
}
//
// @MARK: Actor
@@ -347,10 +379,9 @@ namespace RebootReality.jelycho.Player {
TickCamera();
UpdateAnimator(m_Locomotion.Velocity);
SenseInteractable();
if (m_IsCharging) {
m_ChargeTimer += deltaTime;
m_EquippedItem.Config.chargeAction.OnChargeUpdate(this, m_EquippedItem, GetChargeProgress());
if (m_QuickAttackComboTimer > 0.0f) {
m_QuickAttackComboTimer -= deltaTime;
}
m_SyncRemoteStateTimer -= deltaTime;
@@ -429,6 +460,39 @@ namespace RebootReality.jelycho.Player {
Inventory.TryDrop(command.InventorySlotIndex, out _);
break;
}
case PlayerActorCommands.RequestHandsAnimation: {
PlayerActorRequestHandsAnimationCommand command = new PlayerActorRequestHandsAnimationCommand();
DataSerializationUtils.Deserialize(actorCommand.Data, ref command);
if (m_Animator.HasState(m_HandsLayerIndex, command.AnimationHash)) {
PlayerPlayHandsAnimationEvent handsAnimationEvent = new PlayerPlayHandsAnimationEvent {
AnimationHash = command.AnimationHash
};
SendActorEvent((byte)PlayerActorEvents.PlayHandsAnimation, ref handsAnimationEvent);
} else {
s_Logger.Error($"Animator does not have state with hash {command.AnimationHash}");
}
break;
}
case PlayerActorCommands.DealDamage: {
PlayerActorDealDamageCommand dealDamageCommand = new PlayerActorDealDamageCommand();
DataSerializationUtils.Deserialize(actorCommand.Data, ref dealDamageCommand);
Actor targetActor = RR.FindSpawnedActor(dealDamageCommand.TargetActorID);
if (targetActor == null) {
s_Logger.Error($"Target actor with ID {dealDamageCommand.TargetActorID} not found.");
break;
}
if (targetActor is IKillable killable) {
killable.OnHit(this, 100.0f);
}
break;
}
}
}
@@ -489,6 +553,22 @@ namespace RebootReality.jelycho.Player {
break;
}
case PlayerActorEvents.PlayHandsAnimation: {
if (RR.IsServer()) {
break;
}
PlayerPlayHandsAnimationEvent handsAnimationEvent = new PlayerPlayHandsAnimationEvent();
DataSerializationUtils.Deserialize(actorEvent.Data, ref handsAnimationEvent);
if (m_Animator.HasState(m_HandsLayerIndex, handsAnimationEvent.AnimationHash)) {
m_Animator.CrossFade(handsAnimationEvent.AnimationHash, 0.0f, m_HandsLayerIndex);
} else {
s_Logger.Error($"Animator does not have state with hash {handsAnimationEvent.AnimationHash}");
}
break;
}
default:
s_Logger.Error("Invalid actor event received: " + actorEvent.EventID);
break;
@@ -543,6 +623,17 @@ namespace RebootReality.jelycho.Player {
}
}
public void DealDamage(IKillable target) {
if (target is Actor actor) {
PlayerActorDealDamageCommand dealDamageCommand = new PlayerActorDealDamageCommand {
TargetActorID = actor.ActorID
};
SendActorCommand((byte)PlayerActorCommands.DealDamage, ref dealDamageCommand);
} else {
s_Logger.Error($"Player can only deal damage to other actors!");
}
}
//
// @MARK: Remote
//
@@ -640,7 +731,7 @@ namespace RebootReality.jelycho.Player {
if (m_EquippedItem != null) {
m_EquippedItem.SetHidden(false);
m_EquippedItem.MountTo(this, "hand_right");
m_EquippedItem.MountTo(this, m_EquippedItem.Config.characterEquippedMountSlotName);
}
PlayerActorPrimaryEquippedItemChangedEvent itemChangedEvent =
@@ -709,7 +800,7 @@ namespace RebootReality.jelycho.Player {
//
// @MARK: Sensors
//
bool TryGetBeaconPosition(out Vector3 position) {
public bool TryGetBeaconPosition(out Vector3 position) {
Ray ray = new Ray(m_Camera.Camera.transform.position, m_Camera.Camera.transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, m_BeaconPlacementMaxDistance, m_BeaconPlacementLayerMask) &&
Vector3.Dot(hit.normal, Vector3.up) >= m_NormalDotUpThreshold) {
@@ -721,6 +812,10 @@ namespace RebootReality.jelycho.Player {
return false;
}
public Vector3 GetAttackPosition() {
return m_Camera.transform.position + m_Camera.transform.forward * 1.5f;
}
//
// @MARK: Inventory
//
@@ -797,12 +892,6 @@ namespace RebootReality.jelycho.Player {
public static readonly int VelocityRightNormalized = Animator.StringToHash("VelocityRightNormalized");
public static readonly int TurnVelocity = Animator.StringToHash("TurnVelocity");
public static readonly int IsGrounded = Animator.StringToHash("IsGrounded");
public static readonly int Attack = Animator.StringToHash("Attack");
public static readonly int Block = Animator.StringToHash("Block");
public static readonly int Throw = Animator.StringToHash("Throw");
public static readonly int Holding = Animator.StringToHash("Holding");
}
void UpdateAnimator(Vector3 velocity) {
@@ -822,7 +911,6 @@ namespace RebootReality.jelycho.Player {
m_Animator.SetFloat(AnimatorParamHashes.TurnVelocity, turnVelocity);
m_Animator.SetBool(AnimatorParamHashes.IsGrounded, m_Locomotion.IsGrounded);
m_Animator.SetInteger(AnimatorParamHashes.Holding, 1);
}
}
@@ -875,6 +963,8 @@ namespace RebootReality.jelycho.Player {
DropItem = 0x03,
EquipItem = 0x04,
SelectItemSlot = 0x05,
RequestHandsAnimation = 0x06,
DealDamage = 0x07
}
struct PlayerActorPickupItemCommand : IActorData {
@@ -940,6 +1030,38 @@ namespace RebootReality.jelycho.Player {
return sizeof(int); // SlotIndex
}
}
struct PlayerActorRequestHandsAnimationCommand : IActorData {
public int AnimationHash;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(AnimationHash);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out AnimationHash);
}
public int GetMaxBytes() {
return sizeof(int);
}
}
struct PlayerActorDealDamageCommand : IActorData {
public ushort TargetActorID;
public int GetMaxBytes() {
return sizeof(ushort);
}
public void Serialize(NetworkBufferWriter writer) {
writer.Write(TargetActorID);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out TargetActorID);
}
}
// @MARK: Player Actor Events
enum PlayerActorEvents : byte {
@@ -947,6 +1069,7 @@ namespace RebootReality.jelycho.Player {
PrimaryEquippedItemChanged = 0x01,
UpdatedRemoteState = 0x02,
UpdateInventory = 0x03,
PlayHandsAnimation = 0x04,
}
struct PlayerActorPrimaryEquippedItemChangedEvent : IActorData {
@@ -988,4 +1111,20 @@ namespace RebootReality.jelycho.Player {
return sizeof(byte) + SlotsActorIDs.Length * sizeof(ushort);
}
}
struct PlayerPlayHandsAnimationEvent : IActorData {
public int AnimationHash;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(AnimationHash);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out AnimationHash);
}
public int GetMaxBytes() {
return sizeof(int);
}
}
}

View File

@@ -116,14 +116,6 @@ namespace RebootReality.jelycho.Player {
m_Actor.Jump();
}
if (m_Config.dragObjectActionReference.action.WasPressedThisFrame()) {
m_Actor.StartDrag();
}
if (m_Config.dragObjectActionReference.action.WasReleasedThisFrame()) {
m_Actor.StopDrag();
}
if (m_Config.dropItemActionReference.action.WasReleasedThisFrame()) {
m_Actor.DropItem();
}
@@ -132,6 +124,10 @@ namespace RebootReality.jelycho.Player {
m_Actor.BeginPrimaryAction();
}
if (m_Config.primaryActionReference.action.IsPressed()) {
m_Actor.HoldingPrimaryAction();
}
if (m_Config.primaryActionReference.action.WasReleasedThisFrame()) {
m_Actor.EndPrimaryAction();
}