raycasting and bullet holes help Javascript

The player can still jump even though he is already in the air, cant think why this is

source code:

var walkAceleration: float = 5.0f;
var cameraObject : GameObject;
var rb : Rigidbody;
@HideInInspector
var horizontalMovement : Vector2;
var jumpVelocity : float = 20;
@HideInInspector
var grounded : boolean = false;
var maxSlope : float = 60;

function Start()
{
rb = GetComponent.();
}
function Update ()
{

GetComponent.().velocity.z = Mathf.Clamp(GetComponent.().velocity.z, -20, 20);
GetComponent.().velocity.x = Mathf.Clamp(GetComponent.().velocity.x, -20, 20); //Change the -10 and the 10 to alter movement speed

transform.rotation = Quaternion.Euler(0, cameraObject.GetComponent(MouseLookScript).currentYRotation, 0);
rb.AddRelativeForce(Input.GetAxis(“Horizontal”)* walkAceleration, 0, Input.GetAxis(“Vertical”)* walkAceleration);

if (Input.GetButtonDown(“Jump”))
GetComponent.().AddForce(0,jumpVelocity,0);
}

function OnCollisionStay (collision : Collision)
{
for (var contact : ContactPoint in collision.contacts)
{
if (Vector3.Angle(contact.normal, Vector3.up) < maxSlope)
grounded = true;

}
}
function OnCollisionExit()
{
grounded = false;
}

You need to track if they’ve landed on what you consider the ground. If they haven’t, don’t allow jumping.

I break these things into their own components for my movement ‘styles’ (I have a movementmotor that is a state machine for the various movement styles that an entity can have). I call them ‘resolvers’.

I have resolvers like:
GravityResolver
GroundingResolver
SurfaceResolver (like grounding, but treats walls and ceilings as ground as well… for wall walking and climbing)
JumpResolver

example:
GroundingResolver

using UnityEngine;
using System.Collections.Generic;

using com.spacepuppy;
using com.spacepuppy.Movement;
using com.spacepuppy.Utils;

namespace com.apoc.Movement
{

  [AddComponentMenu("Apoc/Movement/Resolver: Grounding")]
  [RequireComponent(typeof(ApocMovementMotor))]
  public class GroundingResolver : SPNotifyingComponent, IGroundingResolver
  {

  public enum GroundingState
  {
  Unknown = -2,
  Hanging = -1,
  Grounded = 0,
  Jumping = 1,
  Descending = 2,
  Falling = 3
  }

  #region Fields

  [Tooltip("Distance to project below the player to check ground. Should be greater than or equal to the skin width of the attached CharacterController.")]
  public float GroundingSkinWidth = 0.05f;

  public float TerminalFallDistance = 15.0f;

  [Tooltip("Duration of time considered just jumped. This way if we're near the ground, we don't signal as grounded if we're initiating a jump.")]
  public float JustJumpedCooldown = 0.1f;

  [Tooltip("The ground normal is calculated using a CapsuleCast which improperly calculates the surface normal. Set this true to repair the surface normal, only if necessary, as it's a lot more extra work.")]
  public bool RepairSurfaceNormal = false;

  private ApocMovementMotor _motor;

  private Vector2 _groundNormal;
  private GroundingState _currentState;
  private Vector2 _lastGroundedPos;
  private float _lastGroundedTime;
  private float _lastJumpedTime;

  #endregion

  #region CONSTRUCTOR

  protected override void Awake()
  {
  base.Awake();

  _motor = this.GetComponent<ApocMovementMotor>();
  }

  protected override void OnStartOrEnable()
  {
  base.OnStartOrEnable();

  _motor.BeforeUpdateMovement -= this.OnBeforeUpdateMovement;
  _motor.BeforeUpdateMovement += this.OnBeforeUpdateMovement;
  }

  protected override void OnDisable()
  {
  base.OnDisable();

  _motor.BeforeUpdateMovement -= this.OnBeforeUpdateMovement;
  }

  #endregion

  #region IGroundingResolver Interface

  /// <summary>
  /// Returns true if the last ground test trace hit something
  /// </summary>
  public bool IsGrounded
  {
  get { return _groundNormal != Vector2.zero; }
  }

  public Vector2 GroundNormal
  {
  get { return _groundNormal; }
  }

  public Vector2 LastGroundedPosition { get { return _lastGroundedPos; } }

  /// <summary>
  /// The time at which we last calculated being on the ground.
  /// </summary>
  public float LastGroundedTime { get { return _lastGroundedTime; } }

  public Vector2 DesiredJumpNormal { get { return Vector2.up; } }

  public float LastJumpedTime { get { return _lastJumpedTime; } }

  public void SetJumping()
  {
  _currentState = GroundingState.Jumping;
  _lastGroundedPos = _motor.SurfaceConstraint.ProjectPosition2D(this.entityRoot.transform.position);
  _lastJumpedTime = Time.time;
  }

  #endregion

  #region Properties

  public GroundingState CurrentState { get { return _currentState; } }

  /// <summary>
  /// Returns true if the time since the last time jumped is less than JustJumpedCooldown.
  /// </summary>
  public bool JustJumped
  {
  get { return Time.time - _lastJumpedTime < this.JustJumpedCooldown; }
  }

  #endregion

  #region Methods

  public void SetGrounded(Vector2 groundNormal)
  {
  _groundNormal = groundNormal;

  _currentState = GroundingState.Grounded;
  var oldPos = _lastGroundedPos;
  _lastGroundedPos = _motor.SurfaceConstraint.ProjectPosition2D(this.entityRoot.transform.position);

  Notification.PostNotification<LandedNotification>(this, new LandedNotification(_lastGroundedPos, oldPos, this.GroundNormal), true);
  }

  public void SetDropping(bool takeCurrentPositionAsLastGroundedPosition)
  {
  _currentState = GroundingState.Descending;
  if (takeCurrentPositionAsLastGroundedPosition)
  {
  _lastGroundedPos = _motor.SurfaceConstraint.ProjectPosition2D(this.entityRoot.transform.position);
  }
  Notification.PostNotification<DroppedNotification>(this, new DroppedNotification(), true);
  }

  public void SetHanging()
  {
  _currentState = GroundingState.Hanging;
  }






  public GroundingState UpdateGroundingState()
  {
  if (_currentState == GroundingState.Hanging)
  {
  return _currentState;
  }

  if (_currentState == GroundingState.Grounded)
  {
  _lastGroundedPos = _motor.LastPosition;
  if (!this.IsGrounded)
  {
  //LEFT GROUND
  _currentState = GroundingState.Descending;
  }
  }
  else if (_currentState > GroundingState.Grounded)
  {
  if (this.IsGrounded)
  {
  //LANDED
  _currentState = GroundingState.Grounded;
  var oldPos = _lastGroundedPos;
  _lastGroundedPos = _motor.SurfaceConstraint.ProjectPosition2D(this.entityRoot.transform.position);

  Notification.PostNotification<LandedNotification>(this, new LandedNotification(_lastGroundedPos, oldPos, this.GroundNormal), true);
  }
  else if (_currentState == GroundingState.Jumping)
  {
  if (_motor.LastMoveVelocity.y < 0) _currentState = GroundingState.Descending;
  }
  else if (_currentState == GroundingState.Descending)
  {
  if (_lastGroundedPos.y - this.entityRoot.transform.position.y > this.TerminalFallDistance)
  {
  _currentState = GroundingState.Falling;
  Notification.PostNotification<FallingNotification>(this, new FallingNotification(), true);
  }
  }
  else if (_currentState == GroundingState.Falling)
  {

  }

  }
  else
  {
  _currentState = (this.IsGrounded) ? GroundingState.Grounded : GroundingState.Descending;
  _lastGroundedPos = _motor.SurfaceConstraint.ProjectPosition2D(this.entityRoot.transform.position);
  }

  return _currentState;
  }

  /// <summary>
  /// Retests the ground normal and returns true if grounded.
  /// </summary>
  /// <returns></returns>
  public bool UpdateGroundNormal()
  {
  if (this.JustJumped)
  {
  _groundNormal = Vector2.zero;
  return false;
  }

  var geom = _motor.Controller.GetGeom(true);
  var d = this.GroundingSkinWidth + _motor.Controller.SkinWidth;
  RaycastHit hit;
  if (geom.Cast(Vector3.down, out hit, d, Constants.MASK_SURFACE))
  {
  if(this.RepairSurfaceNormal)
  {
  _groundNormal = _motor.SurfaceConstraint.ProjectVectorTo2D(com.spacepuppy.Geom.PhysicsUtil.RepairHitSurfaceNormal(hit, Constants.MASK_SURFACE));
  }
  else
  {
  _groundNormal = _motor.SurfaceConstraint.ProjectVectorTo2D(hit.normal);
  }
  _lastGroundedTime = Time.time;
  }
  else
  {
  _groundNormal = Vector2.zero;
  }

  return _groundNormal != Vector2.zero;
  }

  #endregion

  #region IMovementEnhancer Interface

  private void OnBeforeUpdateMovement(object sender, System.EventArgs e)
  {
  this.UpdateGroundNormal();
  this.UpdateGroundingState();
  }

  #endregion



  #region Notification Types

  public class LandedNotification : Notification
  {

  private Vector2 _pos;
  private Vector2 _lastGroundedPos;
  private Vector2 _groundNormal;
  private float _fallDistance;

  public LandedNotification(Vector2 currentAndLastPos, Vector2 groundNormal)
  {
  _pos = currentAndLastPos;
  _lastGroundedPos = currentAndLastPos;
  _groundNormal = groundNormal;
  _fallDistance = 0f;
  }

  public LandedNotification(Vector2 currentPos, Vector2 lastGroundedPos, Vector2 groundNormal)
  {
  _pos = currentPos;
  _lastGroundedPos = lastGroundedPos;
  _groundNormal = groundNormal;
  _fallDistance = Mathf.Max(0f, _lastGroundedPos.y - _pos.y);
  }

  public Vector2 Position { get { return _pos; } }

  public Vector2 LastGroundedPosition { get { return _lastGroundedPos; } }

  public Vector2 GroundNormal { get { return _groundNormal; } }

  public float FallDistance { get { return _fallDistance; } }

  }

  public class DroppedNotification : Notification
  {

  public DroppedNotification()
  {

  }

  }

  public class FallingNotification : Notification
  {

  public FallingNotification()
  {

  }

  }

  #endregion


  }

}

A little complex as I gather a lot of information, a lot of which may be superficial to general usage.

But used in tandem with one of my JumpResolvers:

using UnityEngine;
using System.Collections.Generic;

using com.spacepuppy;
using com.spacepuppy.Movement;
using com.spacepuppy.Utils;

namespace com.apoc.Movement
{

  [AddComponentMenu("Apoc/Movement/Resolver: Jumping")]
  [RequireComponent(typeof(ApocMovementMotor))]
  [RequireLikeComponent(typeof(IGroundingResolver))]
  public class JumpResolver : SPNotifyingComponent, IDoubleJumpResolver
  {

  #region Fields

  [Tooltip("Duration of time upon leaving the ground that you're still allowed to initialize a jump.")]
  public float JumpInitDelay = 0.1f;

  public float JumpSpeed = 7.0f;

  public bool DoubleJump = false;
  public float DoubleJumpCooldown = 0.1f;
  [Tooltip("Restrict double jump to when you're at some height in your first jump. Defaults to negative infinity to allow double jump at any time.")]
  public float MinimumHeightAllowDoubleJump = float.NegativeInfinity;
  public float DoubleJumpSpeed = 7.0f;

  private ApocMovementMotor _motor;
  private IGroundingResolver _groundingResolver;

  private float _cooldownTimer;
  private bool _bDidDoubleJump = false;

  private bool _bJumpCached;
  private float? _cachedJumpSpeed;

  #endregion

  #region CONSTRUCTOR

  protected override void Awake()
  {
  base.Awake();

  _motor = this.GetComponent<ApocMovementMotor>();
  _groundingResolver = this.GetComponentAlt<IGroundingResolver>();
  }

  protected override void OnStartOrEnable()
  {
  base.OnStartOrEnable();

  Notification.RegisterObserver<GroundingResolver.LandedNotification>(this.entityRoot, this.OnLanded);

  _motor.BeforeUpdateMovement -= this.OnBeforeUpdateMovement;
  _motor.BeforeUpdateMovement += this.OnBeforeUpdateMovement;
  }

  protected override void OnDisable()
  {
  base.OnDisable();

  Notification.RemoveObserver<GroundingResolver.LandedNotification>(this.entityRoot, this.OnLanded);

  _motor.BeforeUpdateMovement -= this.OnBeforeUpdateMovement;
  }

  #endregion

  #region Properties

  public bool IsCoolingDown { get { return _cooldownTimer > 0.0f; } }

  public bool DidDoubleJump { get { return _bDidDoubleJump; } }

  #endregion

  #region Methods
   
  public bool ApplyJump(ref Vector2 mv, float overrideJumpSpeed, bool bDesiresToJump = false)
  {
  if (!this.enabled) return false;
  if (this.IsCoolingDown) return false;

  var t = Time.time;
  if (_groundingResolver.IsGrounded ||
  (t - _groundingResolver.LastJumpedTime > this.JumpInitDelay && t - _groundingResolver.LastGroundedTime < this.JumpInitDelay))
  {
  if (_bJumpCached || bDesiresToJump)
  {
  var spd = (_cachedJumpSpeed != null) ? _cachedJumpSpeed.Value : overrideJumpSpeed;
  //mv.y = spd;

  mv = VectorUtil.SetLengthOnAxis(mv, _groundingResolver.DesiredJumpNormal, spd);
  _bJumpCached = false;
  _cachedJumpSpeed = null;
  _cooldownTimer = this.DoubleJumpCooldown;
  return true;
  }
  }
  else if (this.DoubleJump && !_bDidDoubleJump)
  {
  if (_bJumpCached || bDesiresToJump)
  {
  if (!_groundingResolver.IsGrounded && (_motor.Position.y - _groundingResolver.LastGroundedPosition.y) > this.MinimumHeightAllowDoubleJump)
  {
  var spd = (_cachedJumpSpeed != null) ? _cachedJumpSpeed.Value : overrideJumpSpeed;
  //mv.y = spd;

  mv = VectorUtil.SetLengthOnAxis(mv, _groundingResolver.DesiredJumpNormal, spd);
  _bJumpCached = false;
  _cachedJumpSpeed = null;
  _cooldownTimer = this.DoubleJumpCooldown;
  _bDidDoubleJump = true;

  return true;
  }
  }
  }

  _bJumpCached = false;
  return false;
  }

  public void CacheJump(float overrideJumpSpeed)
  {
  if (this.IsCoolingDown) return;

  _bJumpCached = true;
  _cachedJumpSpeed = overrideJumpSpeed;
  }

  public void ResetCooldown()
  {
  _cooldownTimer = 0.0f;
  }

  #endregion

  #region IJumpResolver Interface

  public bool IsGrounded
  {
  get { return _groundingResolver.IsGrounded; }
  }

  public bool ApplyJump(ref Vector2 mv, bool bDesiresToJump = false)
  {
  if (!this.enabled) return false;
  if (this.IsCoolingDown) return false;

  var t = Time.time;
  if (_groundingResolver.IsGrounded ||
  (t - _groundingResolver.LastJumpedTime > this.JumpInitDelay && t - _groundingResolver.LastGroundedTime < this.JumpInitDelay))
  {
  if (_bJumpCached || bDesiresToJump)
  {
  var spd = (_cachedJumpSpeed != null) ? _cachedJumpSpeed.Value : this.JumpSpeed;
  //mv.y = spd;
  mv = VectorUtil.SetLengthOnAxis(mv, _groundingResolver.DesiredJumpNormal, spd);

  _bJumpCached = false;
  _cachedJumpSpeed = null;
  _cooldownTimer = this.DoubleJumpCooldown;
  return true;
  }
  }
  else if (this.DoubleJump && !_bDidDoubleJump)
  {
  if (_bJumpCached || bDesiresToJump)
  {
  if (!_groundingResolver.IsGrounded && (_motor.Position.y - _groundingResolver.LastGroundedPosition.y) > this.MinimumHeightAllowDoubleJump)
  {
  var spd = (_cachedJumpSpeed != null) ? _cachedJumpSpeed.Value : this.DoubleJumpSpeed;
  //mv.y = spd;
  mv = VectorUtil.SetLengthOnAxis(mv, _groundingResolver.DesiredJumpNormal, spd);

  _bJumpCached = false;
  _cachedJumpSpeed = null;
  _cooldownTimer = this.DoubleJumpCooldown;
  _bDidDoubleJump = true;

  return true;
  }
  }
  }

  _bJumpCached = false;
  return false;
  }

  public void CacheJump()
  {
  if (this.IsCoolingDown) return;

  _bJumpCached = true;
  _cachedJumpSpeed = null;
  }

  public void SignalJumping()
  {
  _groundingResolver.SetJumping();
  Notification.PostNotification<JumpedNotification>(this, JumpedNotification.Create(_bDidDoubleJump), true);
  }

  public void ResetDoubleJump()
  {
  _bDidDoubleJump = false;
  }

  #endregion

  #region Notification Handlers

  private void OnLanded(object sender, GroundingResolver.LandedNotification n)
  {
  this.ResetCooldown();
  this.ResetDoubleJump();
  }

  #endregion

  #region IMovementEnhancer Interface

  private void OnBeforeUpdateMovement(object sender, System.EventArgs e)
  {
  if (_cooldownTimer > 0f)
  {
  _cooldownTimer -= Time.deltaTime;
  if (_cooldownTimer < 0.0f)
  {
  _cooldownTimer = 0.0f;
  }
  }
  }

  #endregion


   
  }

}

Of course each implement an interface of IGroundingResolver and IJumpResolver in case I need to write an alternate version of each.

Then from my movement script I just call ‘ApplyJump’ to my velocity vector, to get jumps added on.

thanks so much, but now I’ve got another issue- my bullet holes are not spawning when i press the left mouse key. I get an error in the console ‘Object reference not set to an instance of an object’. My gunscript is :

var cameraMain : Camera;
@HideInInspector
var cameraaim : Camera;
var fireSpeed : float = 15;
@HideInInspector
var waitTillNextFire : float = 0;
var bullet : GameObject;
var bulletSpawn : GameObject;
function Start () {
cameraMain.enabled = true;
cameraaim.enabled = false;
}
function Update ()
{
if (Input.GetButton(“Fire1”))
{
if (waitTillNextFire <=0)
{
if (bullet)
Instantiate(bullet,bulletSpawn.transform.position, bulletSpawn.transform.rotation);
waitTillNextFire = 1;
}
}
waitTillNextFire -= Time.deltaTime * fireSpeed;

if (Input.GetButtonDown(“Fire2”)){
if ( cameraMain.enabled == true){
cameraaim.enabled = true;
cameraMain.enabled = false;
}
else if (cameraaim.enabled == true){
cameraMain.enabled = true;
cameraaim.enabled = false;

}
}
}

and my bullet script is:

var maxDist : float = 100000;
var decalHitWall : GameObject;
var floatInFrontOfWall : float = 0.0001;

function Update ()
{
var hit : RaycastHit;
if (Physics.Raycast(transform.position, transform.forward, hit, maxDist))
{
if (decalHitWall && hit.transform.tag == “Level”)
Instantiate(decalHitWall, hit.point + (HierarchyType.normal * floatInFrontOfWall), Quaternion.LookRotation(hit.normal));
}
Destroy(gameObject);
}

I have a bullet spawn which is set at the end of the gun barrel and the gun itself has the gunscript attached. I also have a bullethole prefab which is attached to the bullet… sorry if this doesnt make sense didnt know how to phrase it all :slight_smile: