Slimebot Adventure
Game Project 1 Summary
As the first group project, I was expecting chaos and a
crash and burn that I was told about. But, contrary to
my belief, it went fine. We still had some issues, like
not being able to get all the levels in the game. And
also implementing/fixing bugs minutes before the
deadline.
Overall, we made an isometric third
person puzzle solving game. Where you vacuum in slimes
into your slime gun and use the liquid to solve multiple
puzzles.
Check out the game here: Slimebot Adventure
My Contributions
Player Controller
For most of the three weeks, I worked on the player. Implementing isometric movement, the camera movement and also coding the animation transitions. I would look through the many resources on the web for some inspiration. And then implement something similar based on what the designers wanted. Mostly I was fixing bugs that came up and worked a bit on the visual effects. And when I was done with a feature, I would ask for feedback from designers.
public enum MovementType { NewestNewMovement, NewMovement,
OldMovement }
[RequireComponent(typeof(Rigidbody))]
public
class PlayerController : MonoBehaviour
{
public static float GlobalGravity = -9.81f;
public float GravityScale = 1.8f;
public MovementType PlayerMovementType;
public float PlayerSpeed = 10;
public float PlayerTurnSpeed = 25;
public bool PlayerCanJump = false;
public float JumpForce = 12;
public Transform RotationPoint;
public PlayerInputs PlayerControls;
public float CoyoteTime = 0.2f;
public float JumpBufferTime = 0.2f;
public float RayLenght = 1.7f;
public bool HoverThePlayer = true;
public float SpringStrength = 139;
public float SpringDamper = 10;
public float RideHeight = 1.6f;
private Vector3 forward;
private Vector3 right;
private float coyoteTimeCounter;
private float jumpBufferCounter;
private Rigidbody playerRB;
private Vector3 playerInput;
private Vector3 mousePosition;
private bool isGounded = false;
private InputAction moveInput;
private InputAction JumpInput;
private InputAction InteractInput;
private InputAction SuccInput;
private InputAction SprayInput;
private LayerMask platformLayer;
private Vector3 lastPlayerPosition;
private PlayerSoundController playerSoundController;
Vector3 previousTransform;
Vector3 platformVelocity;
private void Awake()
{
playerSoundController =
GetComponent<PlayerSoundController>();
platformLayer =
LayerMask.GetMask("MovingPlatform");
PlayerControls = new PlayerInputs();
}
private void OnEnable()
{
moveInput = PlayerControls.Player.Move;
moveInput.Enable();
JumpInput = PlayerControls.Player.Jump;
JumpInput.Enable();
JumpInput.performed += PlayerJump;
InteractInput = PlayerControls.Player.Interact;
InteractInput.Enable();
InteractInput.performed += InteractWith;
SuccInput = PlayerControls.Player.Succ;
SuccInput.Enable();
SprayInput = PlayerControls.Player.Fire;
SprayInput.Enable();
}
void InteractWith(InputAction.CallbackContext context)
{
if (SprayInput.IsPressed() || SuccInput.IsPressed())
{
Debug.Log("spray is pressed");
return;
}
if (!SprayInput.IsPressed() || !SuccInput.IsPressed())
{
Collider[] colliderArray =
Physics.OverlapSphere(transform.position, 4f);
foreach (Collider collider in colliderArray)
{
if
(collider.gameObject.CompareTag("Interactable"))
{
if (Physics.Linecast(collider.gameObject.transform.position,
transform.position,
LayerMask.GetMask("Default")))
{
Debug.Log("There is something between the interactable
and the player");
}
else
{
Debug.Log("Player interacted with the object");
var interactable =
collider.gameObject.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.Interact();
}
}
}
}
}
}
private void OnDisable()
{
moveInput.Disable();
JumpInput.Disable();
InteractInput.Disable();
SuccInput.Disable();
SprayInput.Disable();
}
private void Start()
{
playerRB = GetComponent<Rigidbody>();
playerRB.useGravity = false;
forward = Camera.main.transform.forward;
forward.y = 0f;
forward = Vector3.Normalize(forward);
right = Quaternion.Euler(new Vector3(0, 90, 0)) *
forward;
}
void GetInput()
{
playerInput = moveInput.ReadValue<Vector2>();
}
void MovePlayer()
{
//New movemnt
switch (PlayerMovementType)
{
case MovementType.NewestNewMovement:
Vector3 newMovement = new Vector3(playerInput.y, 0,
playerInput.x * -1) * PlayerSpeed * Time.deltaTime;
transform.position += newMovement;
break;
case MovementType.NewMovement:
Vector3 movement = new Vector3(playerInput.x, 0,
playerInput.y) * PlayerSpeed * Time.deltaTime;
transform.position += movement;
break;
case MovementType.OldMovement:
Vector3 rightMovement = playerInput.x * right * PlayerSpeed
* Time.deltaTime;
Vector3 upMovement = playerInput.y * forward * PlayerSpeed *
Time.deltaTime;
Vector3 heading = Vector3.Normalize(rightMovement +
upMovement);
//transform.forward = heading;
transform.position += rightMovement;
transform.position += upMovement;
break;
}
}
void PlayerRotation()
{
mousePosition = Input.mousePosition;
Ray cameraRay =
Camera.main.ScreenPointToRay(mousePosition);
Plane groundPlane = new Plane(Vector3.up,
RotationPoint.position);
float rayLenght;
if (groundPlane.Raycast(cameraRay, out rayLenght))
{
Vector3 pointToLook = cameraRay.GetPoint(rayLenght);
//transform.forward = new Vector3(pointToLook.x -
transform.position.x, 0, pointToLook.z -
transform.position.z);
Quaternion targetRotation = Quaternion.LookRotation(new
Vector3(pointToLook.x - transform.position.x, 0,
pointToLook.z - transform.position.z), Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation,
targetRotation, Time.deltaTime * PlayerTurnSpeed);
}
}
void PlayerJump(InputAction.CallbackContext context)
{
jumpBufferCounter = JumpBufferTime;
coyoteTimeCounter = 0f;
}
void Update()
{
if (GameManager.Instance.IsPaused) return;
if (Input.anyKey)
{
GetInput();
}
PlayerRotation();
}
void FixedUpdate()
{
if(GameManager.Instance.IsPaused) return;
if (Physics.Raycast(transform.position,
transform.TransformDirection(Vector3.down), out RaycastHit
platformRay, 5f, platformLayer))
{
if (previousTransform != Vector3.zero &&
previousTransform != platformRay.transform.position)
{
platformVelocity = platformRay.transform.position -
previousTransform;
}
previousTransform = platformRay.transform.position;
if (platformVelocity != null)
{
transform.position += platformVelocity;
}
}
else
{
previousTransform = Vector3.zero;
}
if (moveInput.IsPressed())
{
MovePlayer();
playerSoundController.PlayerAudioSouce.enabled = true;
}
else
{
playerSoundController.PlayerAudioSouce.enabled = false;
}
Vector3 gravity = GlobalGravity * GravityScale *
Vector3.up;
playerRB.AddForce(gravity, ForceMode.Acceleration);
if (lastPlayerPosition.y <= transform.position.y)
{
SpringStrength = 1000;
}
else
{
SpringStrength = 150;
}
lastPlayerPosition = transform.position;
Vector3 otherVel = Vector3.zero;
Vector3 vel = playerRB.velocity;
Debug.DrawLine(transform.position, transform.position + new
Vector3(0, -RayLenght, 0), Color.red);
Debug.DrawLine(transform.position, transform.position + new
Vector3(0, -RideHeight, 0), Color.blue);
Debug.DrawLine(transform.position, transform.position + vel,
Color.white);
if (Physics.Raycast(transform.position,
transform.TransformDirection(Vector3.down), out RaycastHit
rayHit, RayLenght))
{
if
(rayHit.collider.CompareTag("DefaultGround"))
{
playerSoundController.PlayFootstepSound(playerSoundController.DefaultWalkSound);
playerSoundController.PlayerAudioSouce.volume = 0.8f;
}
if (HoverThePlayer)
{
HoverPlayer(rayHit);
}
isGounded = true;
playerRB.velocity = Vector3.zero;
}
else
{
isGounded = false;
}
jumpBufferCounter -= Time.deltaTime;
if (PlayerCanJump && coyoteTimeCounter > 0f
&& jumpBufferCounter > 0f)
{
playerRB.AddForce(Vector3.up * JumpForce,
ForceMode.Impulse);
jumpBufferCounter = 0f;
}
if (PlayerCanJump && isGounded)
{
coyoteTimeCounter = CoyoteTime;
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
}
void HoverPlayer(RaycastHit rayHit)
{
Vector3 vel = playerRB.velocity;
Vector3 rayDir =
transform.TransformDirection(transform.TransformDirection(Vector3.down));
Vector3 otherVel = Vector3.zero;
Rigidbody hitBody = rayHit.rigidbody;
if (hitBody != null)
{
otherVel = hitBody.velocity;
}
float rayDirVel = Vector3.Dot(rayDir, vel);
float otherDirVel = Vector3.Dot(rayDir, otherVel);
float relVel = rayDirVel - otherDirVel;
float x = rayHit.distance - RideHeight;
float springForce = (x * SpringStrength) - (relVel *
SpringDamper);
playerRB.AddForce(rayDir * springForce);
}
}
public class MovementAnimationController : MonoBehaviour
{
public Animator animator;
public PlayerController controller;
PlayerInputs playerInput;
Vector2 currentMovementInput;
Vector3 currentMovement;
bool isMovementPressed;
private Vector3 forward;
private Vector3 right;
private void Awake()
{
playerInput = new PlayerInputs();
playerInput.Player.Move.started += OnMovementInput;
playerInput.Player.Move.canceled += OnMovementInput;
playerInput.Player.Move.performed += OnMovementInput;
}
private void Start()
{
forward = Camera.main.transform.forward;
forward.y = 0f;
forward = Vector3.Normalize(forward);
right = Quaternion.Euler(new Vector3(0, 90, 0)) *
forward;
}
void OnMovementInput(InputAction.CallbackContext context)
{
currentMovementInput =
context.ReadValue<Vector2>();
currentMovement.x = currentMovementInput.x;
currentMovement.z = currentMovementInput.y;
isMovementPressed = currentMovementInput.x != 0 ||
currentMovementInput.y != 0;
}
private void Update()
{
if (GameManager.Instance.IsPaused)
{
animator.speed = 0f;
return;
}
else
{
animator.speed = 1f;
}
HandleWalkAnimations();
HandleAnimation();
HandleWaterGunAnimation();
}
void HandleWalkAnimations()
{
switch (controller.PlayerMovementType)
{
case MovementType.NewestNewMovement:
float newNewVerticalz = Vector3.Dot(currentMovement,
transform.forward);
float newNewHorizontalx = Vector3.Dot(currentMovement,
transform.right);
animator.SetFloat("velocityZ", newNewHorizontalx *
-1, 0.1f, Time.deltaTime);
animator.SetFloat("velocityX", newNewVerticalz,
0.1f, Time.deltaTime);
break;
case MovementType.NewMovement:
float newVerticalz = Vector3.Dot(currentMovement,
transform.forward);
float newHorizontalx = Vector3.Dot(currentMovement,
transform.right);
animator.SetFloat("velocityZ", newVerticalz, 0.1f,
Time.deltaTime);
animator.SetFloat("velocityX", newHorizontalx,
0.1f, Time.deltaTime);
break;
case MovementType.OldMovement:
Vector3 rightMovement = currentMovement.x * right;
Vector3 upMovement = currentMovement.z * forward;
var movement = upMovement + rightMovement;
float verticalz = Vector3.Dot(movement,
transform.forward);
float horizontalx = Vector3.Dot(movement,
transform.right);
animator.SetFloat("velocityZ", verticalz, 0.1f,
Time.deltaTime);
animator.SetFloat("velocityX", horizontalx, 0.1f,
Time.deltaTime);
break;
}
}
void HandleInteractAnimation(InputAction.CallbackContext
context)
{
if
(animator.GetCurrentAnimatorStateInfo(0).IsName("Interaction_State")
&&
animator.GetCurrentAnimatorStateInfo(0).normalizedTime <=
1f)
{
Debug.Log("animation is running");
}
else if(!playerInput.Player.Succ.IsPressed() &&
!playerInput.Player.Fire.IsPressed())
{
animator.SetTrigger("Interacted");
}
}
void HandleWaterGunAnimation()
{
if (playerInput.Player.Succ.IsPressed() ||
playerInput.Player.Fire.IsPressed())
{
animator.SetBool("usingWaterGun", true);
}
else
{
animator.SetBool("usingWaterGun", false);
}
}
void HandleAnimation()
{
bool isWalking = animator.GetBool("isWalking");
if (isMovementPressed && !isWalking)
{
animator.SetBool("isWalking", true);
// Debug.Log("player is walking");
}
else if (!isMovementPressed && isWalking)
{
animator.SetBool("isWalking", false);
// Debug.Log("player is not walking");
}
}
private void OnEnable()
{
playerInput.Player.Enable();
playerInput.Player.Interact.performed +=
HandleInteractAnimation;
}
private void OnDisable()
{
playerInput.Player.Disable();
}
}
My Takeaways
Chaotic deadlines.
We should have utilized QA more,
so that the simple bugs could have been discovered early, so we
could have prevented the chaos during deadlines.
A
better experience than what I was expecting.
Learned
how important it is to convey your problems and opinions to the
team in a cohesive way.