Unity character controller character is not freezing when I stop all movement when he wall grabs (he keeps sliding) - unity3d

Hey guys I'm learning how to create a character controller via the state pattern, which is pretty snazzy, but I'm running into this weird error
I've got a state machine pattern put together so this should be pretty straightforward and easy to handle, but it's kinda confusing. I tried freezing him in the state to see if he was transitioning from wallgrab to wallslide, but when I stop the velocity on the x and y he still slides. There's no other movement physics being applied at this time (why I'd recommend the state pattern), but for some reason his movement isn't being frozen. I can't pin a reason as to why, other than point you to my code and my github, and hope someone like my pixel art and has fun with the movement for a little bit.
All the code is in the scripts folder within the assets folder, and the states are within Assets > Scripts > Player > and then there are two folders (sub and super) The WallGrabState inherits from the PlayerTouchingWallState, and the logic is coming mainly from the WallGrabState for the physics. I'm just setting the x and y to zero, and yet he still slides, and idk what to do.
PlayerTouchingWall inherits the main state logic from playerState, and the player script loads all the states together and holds the physics and miscellaneous functions. The link to my github project is here https://github.com/roninMo/2d-Intro-Unity-Project
Here's the PlayerWallSlideState:
using UnityEngine;
public class PlayerWallSlideState : PlayerTouchingWallState
{
public PlayerWallSlideState(Player player, PlayerStateMachine stateMachine, PlayerData playerData, string currentAnimation) : base(player, stateMachine, playerData, currentAnimation)
{
}
public override void LogicUpdate()
{
base.LogicUpdate();
player.SetVelocityY(-playerData.wallslideVelocity);
// State logic
if (grabInput && input.y > 0)
{
player.StateMachine.ChangeState(player.wallGrabState);
}
}
}
And Here's the PlayerTouchingWallState:
using UnityEngine;
public class PlayerTouchingWallState : PlayerState
{
protected bool isTouchingGround;
protected bool isTouchingWall;
protected Vector2 input;
protected bool grabInput;
public PlayerTouchingWallState(Player player, PlayerStateMachine stateMachine, PlayerData playerData, string currentAnimation) : base(player, stateMachine, playerData, currentAnimation)
{
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void LogicUpdate()
{
base.LogicUpdate();
input = player.InputHandler.RawMovementInput;
grabInput = player.InputHandler.GrabInput;
// State logic
if (isTouchingGround && !grabInput)
{
StateMachine.ChangeState(player.IdleState);
}
else if (!isTouchingWall || (input.x != player.FacingDirection && !grabInput))
{
StateMachine.ChangeState(player.InAirState);
}
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
}
public override void DoChecks()
{
base.DoChecks();
isTouchingGround = player.CheckIfTouchingGround();
isTouchingWall = player.CheckIfTouchingWall();
}
}

The problem is that unity still applys physics to the character. I stopped it by grabbing the transform, and setting the player the position it entered in this state, along with freezing the velocity. Setting the transform is enough, but the camera was dragging upwards in place... So freezing the velocity fixed that. Anyways here's the code:
using UnityEngine;
public class PlayerWallGrabState : PlayerTouchingWallState
{
private Vector2 holdPosition;
public PlayerWallGrabState(Player player, PlayerStateMachine stateMachine, PlayerData playerData, string currentAnimation) : base(player, stateMachine, playerData, currentAnimation)
{
}
public override void Enter()
{
base.Enter();
holdPosition = player.transform.position;
HoldPosition();
}
public override void Exit()
{
base.Exit();
}
public override void LogicUpdate()
{
base.LogicUpdate();
HoldPosition();
// State logic
if (input.y > 0)
{
StateMachine.ChangeState(player.wallClimbState);
}
else if (input.y < 0 || !grabInput)
{
StateMachine.ChangeState(player.wallSlideState);
}
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
}
public override void DoChecks()
{
base.DoChecks();
}
private void HoldPosition()
{
player.transform.position = holdPosition;
player.SetVelocityX(0f);
player.SetVelocityY(0f);
}
}

Related

Unity's Open XR inputs TryGetFeatureValue always 0 for all Unity XR Input's CommonUsages

So I have been debugging for hours at this point to no avail. I call my function in another class and it keeps returning 0 I have tried logging everything to see if there is an error with it, and I can't find one. The target device prints out as UnityEngine.XR.InputDevice and nothing errors or warns. please if anyone has any insight. here's how I call it
Debug.Log(RightHand.AButtonDown());
and here is my code for the functions
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public static class RightHand
{
private static InputDevice targetDevice;
static RightHand()
{
TryInitialize();
}
private static void TryInitialize()
{
Debug.Log("ran inital");
List<InputDevice> devices = new List<InputDevice>();
InputDeviceCharacteristics rightControllerCharacteristics = InputDeviceCharacteristics.Right | InputDeviceCharacteristics.Controller;
InputDevices.GetDevicesWithCharacteristics(rightControllerCharacteristics, devices);
foreach (var item in devices)
{
Debug.Log("ran log");
Debug.Log(item.name + item.characteristics);
}
Debug.Log("right controler characteristics" + rightControllerCharacteristics);
if (devices.Count > 0)
{
targetDevice = devices[0];
}
Debug.Log(targetDevice);
}
public static bool AButtonDown()
{
targetDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool primaryButtonOut);
if (primaryButtonOut)
{
return true;
}
else
{
return false;
}
}
}
My best guess is the list of devices needs to be updated each time a device value is accessed. The method I use uses Unity's XR Toolkit and had the same problem as yours until I moved InputDevice assignment into Update.
using UnityEngine.XR;
public class InputDeviceSample : MonoBehaviour
{
InputDevice left;
InputDevice right;
void Start()
{
}
void Update()
{
// needs to be in Update
right = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
// left = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
// more https://docs.unity3d.com/ScriptReference/XR.XRNode.html
// assigns button value to out variable, if expecting Vector3 replace bool
right.TryGetFeatureValue(CommonUsages.triggerButton, out bool isPressed);
Debug.Log(isPressed);
}
}
CommonUsages is the list of input actions you can read in, here's a list with brief descriptions:
https://docs.unity3d.com/ScriptReference/XR.CommonUsages.html

Accessing variable in another script, The name 'isAnimated' does not exist in the current context

I'm trying to access a boolean from another script, which I using a custom namespace. The idea is to have a checkbox in the inspector per item if it should be animated or not. On a weaponmesh, I have a materialoffset animator that should trigger if that box is checked.
using MFPSEditor;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MFPS.Internal.Structures;
namespace MFPS.Addon.Customizer
{
At the bottom of this script, I have this:
[System.Serializable]
public class GlobalCamo
{
public string Name;
[SpritePreview(50)] public Texture2D Preview;
public MFPSItemUnlockability Unlockability;
public string Description;
public bool isAnimated;
public int Price
{
get => Unlockability.Price;
}
public bool animatedCamo()
{
return isAnimated;
}
public bool isFree() { return Price <= 0; }
[HideInInspector] public Sprite m_sprite = null;
public Sprite spritePreview()
{
if (m_sprite == null && Preview != null)
{
m_sprite = Sprite.Create(Preview, new Rect(0, 0, Preview.width, Preview.height), new Vector2(0.5f, 0.5f));
}
return m_sprite;
}
}
}
I am trying to access the variable "isAnimated" in the script below:
using UnityEngine;
public class MaterialOffsetAnimator : MonoBehaviour
{
// The material that will be animated
Material material;
// The speed at which the offset will be animated
public Vector2 animationSpeed;
void Update()
{
material = GetComponent<Renderer>().material;
if ( isAnimated == true )
{
// Update the material's offset based on the animation speed
material.mainTextureOffset += animationSpeed * Time.deltaTime;
}
else
{
}
}
}
isAnimated is an instance field in your GlobalCamo class, not a static field. You can't access it without a reference to an instance of GlobalCamo.
You can just make the isAnimated field static, like this:
public static bool isAnimated;
And then access it in MaterialOffsetAnimator like this:
if (GlobalCamo.isAnimated)
{
// Your code
}
Also, a side note: You don't need to use == true with booleans. If a boolean is true, the condition will evaluate to true.

How to network a VR button with PUN in Unity?

I've been working on a solution for weeks now and I think it's time to reach out. I'm trying to make a button that plays a sound when pressed by a controller, everyone will hear that sound. Using VRTK and PlayoVR, I'm able to make a non-networked version where the player can put their hand through a cube, click the trigger from the controller, and it makes a sound.
This is the code for that cube:
namespace VRTK.Examples {
using UnityEngine;
public class Whirlygig : VRTK_InteractableObject
{
public GameObject AudioSource;
public AudioSource LeftSpeaker;
public override void StartUsing(VRTK_InteractUse currentUsingObject =
null)
{
AudioSource.GetComponent<AudioSource>().Play();
}
}
}
Where I get lost is how to network it with Photon Unity Networking. This is what I have:
namespace PlayoVR
{
using UnityEngine;
using VRTK;
using UnityEngine.Video;
using NetBase;
public class PlaySync : Photon.MonoBehaviour
{
public AudioSource LeftSpeaker;
public GameObject Whirlgig;
private bool StartUsing;
// Use this for initialization
void Awake()
{
GetComponent<VRTK_InteractableObject>().InteractableObjectUsed +=
new InteractableObjectEventHandler(DoPlay);
}
void DoPlay(object sender, InteractableObjectEventArgs e)
{
StartUsing = true;
}
// Update is called once per frame
void Update()
{
// Handle firing
if (StartUsing)
{
CmdPlay();
StartUsing = false;
}
}
void CmdPlay()
{
photonView.RPC("NetPlay", PhotonTargets.All);
}
[PunRPC]
void NetPlay()
{
LeftSpeaker.Play();
}
}
}
As you can probably see, I'm a beginner. With this code, when I put my hand in the cube and press the trigger, nothing happens. If anyone can provide any help or even an alternative, I'd be very grateful.
Kind regards,
TheMusiken

Multiplayer [SyncEvent] problem with non-player objects

I'm developing a multiplayer game based on turns.
So I have an script named gameController which is the one who has the global timer to alter the turns and also choose which players are attacking and which players are defending in the current turn.
This script is a singleton attached to a gameObject with a network identity on it (This network identity has no checkboxes marked to be controller by the server). I tried to have this game object spawned to all the clients when the server connects and also to have the game object already in the scene (booth cases aren't working).
Well, the main problem is that in the gameController script I have a checker in the update function to check if any new player is connected. In case is connected, it should call a syncEvent named EventOnNewPlayerAddedDelegate (I tried to call it directly, also using [command] and using [ ClientRpc]) to let the players know that they have to call their function named "OnRegisterPlayer", which is a function in their own player script that calls a function on gameController script, passing the player object (I tried via command and rpc also), something like this: GameController.instance.RegisterPlayer(this);
So anyone knows how can I trigger this SyncEvent to register the player to a non-player object controlled by the server?
Thank you very much.
I attach here a brief of booth scripts to make it easier to understand:
GameController:
public class GameController : NetworkBehaviour
{
public delegate void OnNewPlayerAddedDelegate();
[SyncEvent]
public event OnNewPlayerAddedDelegate EventOnNewPlayerAddedDelegate;
List<GamePlayerManager> players = new List<GamePlayerManager>();
public static GameController instance { get; private set; }
float timePerTorn = 30f;
bool isCountdown = false;
[System.NonSerialized]
public float countdownTime;
int roundNumber = 0;
int NumberOfPlayers;
int NumberOfPlayersChanged;
void Awake()
{
Debug.Log("Awaking ");
if (instance != null)
{
Debug.Log("Destoring ");
DestroyImmediate(this);
return;
}
instance = this;
}
// Use this for initialization
void Start()
{
if (!isServer)
{
return;
}
players = new List<GamePlayerManager>();
StartCountdown(5f);//20
}
void Update()
{
if (isServer)
{
if (isCountdown)
{
countdownTime -= Time.deltaTime;
if (countdownTime <= 0)
{
AlterGlobalTurns();
}
}
}
NumberOfPlayers = NetworkManager.singleton.numPlayers;
if (NumberOfPlayersChanged != NumberOfPlayers)
{
Debug.Log("num of players changed ---> " + NumberOfPlayers);
NumberOfPlayersChanged = NumberOfPlayers;
EventOnNewPlayerAddedDelegate();
//RpcNewPlayerAdded();
//CmdNewPlayerAdded();
}
}
[ClientRpc]
void RpcNewPlayerAdded()
{
Debug.Log("---------------- RpcNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
[Command]
void CmdNewPlayerAdded()
{
Debug.Log("---------------- CmdNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
public void RegisterPlayer(GamePlayerManager player)
{
Debug.Log("player ---> " + player.name);
if (players.Contains(player))
{
return;
}
Debug.Log("players ---> " + players);
players.Add(player);
}
}
PlayerScript:
public class GamePlayerManager : NetworkBehaviour
{
[System.NonSerialized]
public bool isPlayingOnTorn = true;
void Awake()
{
GameController.instance.EventOnNewPlayerAddedDelegate += OnRegisterPlayer;
}
private void Start()
{
if (!isLocalPlayer)
{
return;
}
}
public override void OnStartServer()
{
GameObject gc = (GameObject)Instantiate(NetworkManager.singleton.spawnPrefabs[2], transform.position, transform.rotation);
NetworkServer.Spawn(gc);
}
void OnRegisterPlayer(){
if (isLocalPlayer)
{
//GameController.instance.RegisterPlayer(this);
//RpcRegisterPlayer();
CmdRegisterPlayer();
}
}
[Command]
void CmdRegisterPlayer(){
Debug.Log("-------------Command Register player -------------");
GameController.instance.RegisterPlayer(this);
}
[ClientRpc]
void RpcRegisterPlayer()
{
Debug.Log("------------- RPC REgister Player -------------");
GameController.instance.RegisterPlayer(this);
}
}
I think I already saw the problem here.
The problem is that GameController is spawned by the playerScript(just if the player is also the host) in the function OnStartServer(). So the first problem is that the GameController doesn't detect the host player because it is not a new connections. And the second problem is when a second client is connected, the GameController detect the client connection and send the event signal faster than the client wakeup, so when the client is already working the signal is gone.
I solved the problem deleting this signal and checking directly in the playerScript if the GameController exists then check if the player is already registered.
My question now is that if there is anyway to instance and awake the server objects before the player(Host), which I understand that the answer is "no" because as the player is the host it needs to be running to have the server.
And the second question is if there is anyway or any signal to know that a new player is connected, and wait until it is awaked.
Thank you very much.
You can check the hole thread at Unity forums: https://forum.unity.com/threads/multiplayer-syncevent-problem-with-non-player-objects.589309/

How to use multiple player prefabs for Unity's new UNET?

Has anyone got multiple player prefabs working (eg different character classes with different prefabs) on the new Unity UNET Networking?
Finally got it working!
Massive thank you to #ClausKleber for their answer at
http://forum.unity3d.com/threads/how-to-set-individual-playerprefab-form-client-in-the-networkmanger.348337/#post-2256378
Edited working version below, Works with the Network Manager HUD to create and join.
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class NetManagerCustom : NetworkManager
{
// in the Network Manager component, you must put your player prefabs
// in the Spawn Info -> Registered Spawnable Prefabs section
public short playerPrefabIndex;
public override void OnStartServer()
{
NetworkServer.RegisterHandler(MsgTypes.PlayerPrefab, OnResponsePrefab);
base.OnStartServer();
}
public override void OnClientConnect(NetworkConnection conn)
{
client.RegisterHandler(MsgTypes.PlayerPrefab, OnRequestPrefab);
base.OnClientConnect(conn);
}
private void OnRequestPrefab(NetworkMessage netMsg)
{
MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
msg.controllerID = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>().controllerID;
msg.prefabIndex = playerPrefabIndex;
client.Send(MsgTypes.PlayerPrefab, msg);
}
private void OnResponsePrefab(NetworkMessage netMsg)
{
MsgTypes.PlayerPrefabMsg msg = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>();
playerPrefab = spawnPrefabs[msg.prefabIndex];
base.OnServerAddPlayer(netMsg.conn, msg.controllerID);
Debug.Log(playerPrefab.name + " spawned!");
}
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
msg.controllerID = playerControllerId;
NetworkServer.SendToClient(conn.connectionId, MsgTypes.PlayerPrefab, msg);
}
// I have put a toggle UI on gameObjects called PC1 and PC2 to select two different character types.
// on toggle, this function is called, which updates the playerPrefabIndex
// The index will be the number from the registered spawnable prefabs that
// you want for your player
public void UpdatePC ()
{
if (GameObject.Find("PC1").GetComponent<Toggle>().isOn)
{
playerPrefabIndex = 3;
}
else if (GameObject.Find("PC2").GetComponent<Toggle>().isOn)
{
playerPrefabIndex= 4;
}
}
}
Create a new class that derives from the built-in NetworkManager script. In there, just add a few supporting fields and an override for OnServerAddPlayer().
[SerializeField] Vector3 playerSpawnPos;
[SerializeField] GameObject character1;
[SerializeField] GameObject character2;
// etc.
GameObject chosenCharacter; // character1, character2, etc.
// Instantiate whichever character the player chose and was assigned to chosenCharacter
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) {
var player = (GameObject)GameObject.Instantiate(chosenCharacter, playerSpawnPos, Quaternion.identity);
NetworkServer.AddPlayer(conn, player, playerControllerId);
}
Reference: http://docs.unity3d.com/Manual/UNetManager.html