Bar Rescue!
Original PlateUp game image
PlateUp! is a cooperative cooking and restaurant management game where players team up to run a bustling eatery. Combining elements of strategy, time management, and teamwork, players must prepare and serve a variety of dishes, manage customer orders, and keep the kitchen running smoothly under increasing pressure. Each day brings new challenges and upgrades, making for a dynamic and engaging gameplay experience as players strive to build a successful restaurant.
I really enjoyed playing PlateUp! and it inspired me to create my own version for a project. The game’s combination of cooperative cooking and restaurant management, with its strategic elements and fast-paced gameplay, made it an exciting and engaging experience. My goal is to recreate this dynamic and fun environment, focusing on teamwork and efficient kitchen management, while adding my own unique twists to make the game even more enjoyable.
Original PlateUp game image
How far along am I with the project?
I am currently working on this project stay tuned to see the progress.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour, IKitchenObjectParent
{
public static Player Instance { get; private set; } // this is a property ( Singleton patern ), this makes is public to get from other classes but privet to set it.
public event EventHandler<OnSelectedCounterChangedEventArgs> OnSelectedCounterChanged;
public class OnSelectedCounterChangedEventArgs : EventArgs {
public BaseCounter selectedCounter;
}
[SerializeField] private float moveSpeed = 7f;
[SerializeField] private float rotateSpeed = 15f;
[SerializeField] private GameInput gameInput;
const string GAME_INPUT = "GameInput";
[SerializeField] private LayerMask countersLayerMask;
[SerializeField] private Transform kitchenObjectHoldPoint;
private bool isWalking; // For the animaitor
private Vector3 lastInteractDir;
private BaseCounter selectedCounter;
private KitchenObject kitchenObject;
/// <summary>
/// you do not want to define the move direction as a local variable because it won't work as good with the
/// move direction. So that is why it is defined twice.
/// </summary>
///
private void Awake()
{
if(Instance != null)
{
Debug.LogError("THERE IS MORE THAN ONE PLAYER");
}
Instance = this;
gameInput = GameObject.Find(GAME_INPUT).GetComponent<GameInput>();
}
private void Start()
{
gameInput.OnInteractAction += GameInput_OnInteractAction;
gameInput.OnInteractAlternateAction += GameInput_OnInteractAlternateAction;
}
private void GameInput_OnInteractAlternateAction(object sender, EventArgs e)
{
if (selectedCounter != null)
{
selectedCounter.InteractAlternate(this);
}
}
private void GameInput_OnInteractAction(object sender, System.EventArgs e)
{
if(selectedCounter != null)
{
selectedCounter.Interact(this);
}
}
private void Update()
{
HandleMovement();
HandleInteraction();
}
public bool IsWalking()
{
return isWalking;
}
private void HandleInteraction()
{
// Get the interact input from the InputSystem.
Vector2 inputVector = gameInput.GetNormalizedMovementVector();
// Put the input inside vector, to check which direction we are looking.
Vector3 moveDir = new Vector3(inputVector.x, 0f, inputVector.y);
if (moveDir != Vector3.zero) // We are moving
{
lastInteractDir = moveDir;
}
float interactDistance = 2f; // Define the interact distance ( We don't want to change this that is why it is defined here.)
if (Physics.Raycast(transform.position, lastInteractDir, out RaycastHit raycastHit, interactDistance, countersLayerMask))// Returns a boolean if it hits something
{
if (raycastHit.transform.TryGetComponent(out BaseCounter baseCounter)) // Checks en try's to get the baseCounter componenet otherwise it returns null
{
// Has baseCounter
if (baseCounter != selectedCounter)
{
//Debug.Log("CLEARCOUNTER != SELECTEDCOUNTER");
SetSelectedCounter(baseCounter);
}
}
else
{
SetSelectedCounter(null);
}
}
else
{
SetSelectedCounter(null);
}
}
private void HandleMovement()
{
// Get the move input via the new input system
Vector2 inputVector = gameInput.GetNormalizedMovementVector();
// Put the move input in a vector
Vector3 moveDir = new Vector3(inputVector.x, 0f, inputVector.y);
// set value's we need in the Capsule Cast. We don't want to change these values that is why we define them here.
float moveDistance = moveSpeed * Time.deltaTime;
float playerRadius = .8f;
float playerHeigth = 2f;
// This checks if there is an obstacle in the way.
bool canMove = !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeigth, playerRadius, moveDir, moveDistance);
if (!canMove)
{
// Cannot move towards movedir, Try only X movement
Vector3 moveDirX = new Vector3(moveDir.x, 0, 0).normalized;
canMove = moveDir.x != 0 && !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeigth, playerRadius, moveDirX, moveDistance);
if (canMove)
{
// Can move only on the X
moveDir = moveDirX;
}
else
{
// Cannot move on the X, Try only on Z movement
Vector3 moveDirZ = new Vector3(0, 0, moveDir.z).normalized;
canMove = moveDir.z != 0 && !Physics.CapsuleCast(transform.position, transform.position + Vector3.up * playerHeigth, playerRadius, moveDirZ, moveDistance);
if (canMove)
{
// Move only in the Z
moveDir = moveDirZ;
}
else
{
// Can not move in any direction.
}
}
}
if (canMove)
{
transform.position += moveDir * moveDistance;
}
isWalking = moveDir != Vector3.zero;
transform.forward = Vector3.Slerp(transform.forward, moveDir, Time.deltaTime * rotateSpeed); // Makes the player rotate smoothly
}
private void SetSelectedCounter(BaseCounter selectedCounter) // This fires of the event of the selected counter
{
this.selectedCounter = selectedCounter;
OnSelectedCounterChanged?.Invoke(this, new OnSelectedCounterChangedEventArgs
{
selectedCounter = selectedCounter
});
}
// This function returns the transfom that holds the object. (Hands position)
public Transform GetKitchenObjectFollowTransform()
{
return kitchenObjectHoldPoint;
}
// This sets the grabbed object to player 'kitchenObjectHoldPoint' position
public void SetKitchenObject(KitchenObject kitchenObject) // here you can place some animaton for grabbing!?
{
this.kitchenObject = kitchenObject;
}
public KitchenObject GetKitchenObject()
{
return kitchenObject;
}
public void ClearKitchenObject()
{
kitchenObject = null;
}
public bool HasKitchenObject()
{
return kitchenObject != null;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseCounter : MonoBehaviour, IKitchenObjectParent
{
[SerializeField] private Transform counterTopPoint;
public virtual void Interact(Player player)
{
Debug.LogError("BaseCounter.Interact()");
}
public virtual void InteractAlternate(Player player)
{
}
private KitchenObject kitchenObject;
public Transform GetKitchenObjectFollowTransform()
{
return counterTopPoint;
}
public void SetKitchenObject(KitchenObject kitchenObject)
{
this.kitchenObject = kitchenObject;
}
public KitchenObject GetKitchenObject()
{
return kitchenObject;
}
public void ClearKitchenObject()
{
kitchenObject = null;
}
public bool HasKitchenObject()
{
return kitchenObject != null;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ContainerCounter : BaseCounter
{
public event EventHandler OnPlayerGrabbedObject;
[SerializeField] private KitchenObjectSO kitchenObjectSO;
public override void Interact(Player player)
{
if (!player.HasKitchenObject()) // Player is not carrying anything so give it one object.
{
KitchenObject.SpawnKitchenObject(kitchenObjectSO, player);
OnPlayerGrabbedObject?.Invoke(this, EventArgs.Empty);
//StartCoroutine(IE_Interact(player));
}
}
/// <summary>
/// You can put everything in an Coroutine to make it wait over time, perhaps for a draft of something.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
//private IEnumerator IE_Interact(Player player)
//{
// yield return new WaitForSeconds(3f);
//}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CuttingCounter : BaseCounter , IHasProgress
{
public event EventHandler<IHasProgress.OnProgressChangedEventArgs> OnProgressChanged;
public event EventHandler OnCut;
[SerializeField] private CuttingRecipeSO[] cuttingRecipeSOArray;
private int cuttingProgress;
public override void Interact(Player player)
{
// Pick up and handle items
if (!HasKitchenObject())
{
// There is no kitchenobject here
if (player.HasKitchenObject())
{
// Player is carrying something
if (HasRecipeWithInput(player.GetKitchenObject().GetKitchenObjectSO()))
{
// Player is carrying something that can be cut.
player.GetKitchenObject().SetKitchenObjectParent(this);
cuttingProgress = 0;
CuttingRecipeSO cuttingRecipeSO = GetCuttingRecipeSOWithInput(GetKitchenObject().GetKitchenObjectSO());
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs // Execute event to show manipulate the progressbar. make sure to cast cuttingprogress to a float otherwise you will get 0 because you are devidign 2 ints.
{
progressNormalized = (float)cuttingProgress / cuttingRecipeSO.cuttingProgressMax
});
}
}
else
{
// Player not carrying anything
}
}
else
{
// There is a kitchenobject here
if (player.HasKitchenObject())
{
// Player is carrying something
}
else
{
// Player is not carrying anything
GetKitchenObject().SetKitchenObjectParent(player);
}
}
}
public override void InteractAlternate(Player player)
{
if (HasKitchenObject() && HasRecipeWithInput(GetKitchenObject().GetKitchenObjectSO()))
{
// There is a KitchenObject here AND it can be cut.
cuttingProgress++;
OnCut?.Invoke(this, EventArgs.Empty); // This fires the cutting animation
CuttingRecipeSO cuttingRecipeSO = GetCuttingRecipeSOWithInput(GetKitchenObject().GetKitchenObjectSO());
// Execute event to show manipulate the progressbar. make sure to cast cuttingprogress to a float otherwise you will get 0 because you are deviding 2 ints.
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = (float)cuttingProgress / cuttingRecipeSO.cuttingProgressMax
});
if (cuttingProgress >= cuttingRecipeSO.cuttingProgressMax)
{
// this gives us the correct output from the getkitcheobject input.
KitchenObjectSO outputKitchenObjectSO = GetOutPutForInput(GetKitchenObject().GetKitchenObjectSO());
GetKitchenObject().DestroySelf();
KitchenObject.SpawnKitchenObject(outputKitchenObjectSO, this);
}
}
}
private bool HasRecipeWithInput(KitchenObjectSO inputKitchenObjectSO)
{
CuttingRecipeSO cuttingRecipeSO = GetCuttingRecipeSOWithInput(inputKitchenObjectSO);
return cuttingRecipeSO != null;
}
private KitchenObjectSO GetOutPutForInput(KitchenObjectSO inputKitchenObjectSO)
{
CuttingRecipeSO cuttingRecipeSO = GetCuttingRecipeSOWithInput(inputKitchenObjectSO);
if(cuttingRecipeSO != null)
{
return cuttingRecipeSO.output;
}
else
{
return null;
}
}
private CuttingRecipeSO GetCuttingRecipeSOWithInput(KitchenObjectSO inputKitchenObjectSO)
{
foreach (CuttingRecipeSO cuttingRecipeSO in cuttingRecipeSOArray)
{
if (cuttingRecipeSO.input == inputKitchenObjectSO)
{
return cuttingRecipeSO;
}
}
return null;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StoveCounter : BaseCounter, IHasProgress
{
public event EventHandler<IHasProgress.OnProgressChangedEventArgs> OnProgressChanged;
public event EventHandler<OnStateChangedEverntArgs> OnStateChanged;
public class OnStateChangedEverntArgs : EventArgs
{
public State state;
}
public enum State
{
Idle,
Frying,
Fried,
Burned,
}
[SerializeField] private FryingRecipeSO[] fryingRecipeSOArray;
[SerializeField] private BurningRecipeSO[] burningRecipeSOArray;
private State state;
private float fryingTimer;
private FryingRecipeSO fryingRecipeSO;
private float burningTimer;
private BurningRecipeSO burningRecipeSO;
private void Start()
{
state = State.Idle;
}
private void Update()
{
if (HasKitchenObject())
{
switch (state)
{
case State.Idle:
break;
case State.Frying:
fryingTimer += Time.deltaTime;
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = fryingTimer / fryingRecipeSO.fryingTimerMax
});
if (fryingTimer > fryingRecipeSO.fryingTimerMax)
{
GetKitchenObject().DestroySelf();
KitchenObject.SpawnKitchenObject(fryingRecipeSO.output, this);
state = State.Fried;
burningTimer = 0f;
burningRecipeSO = GetBurningRecipeSOWithInput(GetKitchenObject().GetKitchenObjectSO());
OnStateChanged?.Invoke(this, new OnStateChangedEverntArgs
{
state = state
});
}
break;
case State.Fried:
burningTimer += Time.deltaTime;
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = burningTimer / burningRecipeSO.burningTimerMax
});
if (burningTimer > burningRecipeSO.burningTimerMax)
{
GetKitchenObject().DestroySelf();
KitchenObject.SpawnKitchenObject(burningRecipeSO.output, this);
state = State.Burned;
OnStateChanged?.Invoke(this, new OnStateChangedEverntArgs
{
state = state
});
}
break;
case State.Burned:
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = 0f
});
OnStateChanged?.Invoke(this, new OnStateChangedEverntArgs
{
state = state
});
break;
}
Debug.Log(state);
}
}
public override void Interact(Player player)
{
// Pick up and handle items
if (!HasKitchenObject())
{
// There is no kitchenobject here
if (player.HasKitchenObject())
{
// Player is carrying something
if (HasRecipeWithInput(player.GetKitchenObject().GetKitchenObjectSO()))
{
// Player is carrying something that can be fried.
player.GetKitchenObject().SetKitchenObjectParent(this);
fryingRecipeSO = GetFryingRecipeSOWithInput(GetKitchenObject().GetKitchenObjectSO());
state = State.Frying;
fryingTimer = 0f;
OnStateChanged?.Invoke(this, new OnStateChangedEverntArgs
{
state = state
});
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = fryingTimer / fryingRecipeSO.fryingTimerMax
});
}
}
else
{
// Player not carrying anything
}
}
else
{
// There is a kitchenobject here
if (player.HasKitchenObject())
{
// Player is carrying something
}
else
{
// Player is not carrying anything
GetKitchenObject().SetKitchenObjectParent(player);
state = State.Idle;
OnStateChanged?.Invoke(this, new OnStateChangedEverntArgs
{
state = state
});
OnProgressChanged?.Invoke(this, new IHasProgress.OnProgressChangedEventArgs
{
progressNormalized = 0f
});
}
}
}
private bool HasRecipeWithInput(KitchenObjectSO inputKitchenObjectSO)
{
FryingRecipeSO fryingRecipeSO = GetFryingRecipeSOWithInput(inputKitchenObjectSO);
return fryingRecipeSO != null;
}
private KitchenObjectSO GetOutPutForInput(KitchenObjectSO inputKitchenObjectSO)
{
FryingRecipeSO fryingRecepeSO = GetFryingRecipeSOWithInput(inputKitchenObjectSO);
if (fryingRecepeSO != null)
{
return fryingRecepeSO.output;
}
else
{
return null;
}
}
private FryingRecipeSO GetFryingRecipeSOWithInput(KitchenObjectSO inputKitchenObjectSO)
{
foreach (FryingRecipeSO fryingRecipeSO in fryingRecipeSOArray)
{
if (fryingRecipeSO.input == inputKitchenObjectSO)
{
return fryingRecipeSO;
}
}
return null;
}
private BurningRecipeSO GetBurningRecipeSOWithInput(KitchenObjectSO inputKitchenObjectSO)
{
foreach (BurningRecipeSO burningRecipeSO in burningRecipeSOArray)
{
if (burningRecipeSO.input == inputKitchenObjectSO)
{
return burningRecipeSO;
}
}
return null;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class KitchenObject : MonoBehaviour
{
[SerializeField] private KitchenObjectSO KitchenObjectSO;
private IKitchenObjectParent kitchenObjectParent;
public KitchenObjectSO GetKitchenObjectSO()
{
return KitchenObjectSO;
}
public void SetKitchenObjectParent(IKitchenObjectParent kitchenObjectParent)
{
if(this.kitchenObjectParent != null) // this refers to the previouse clear counter
{
this.kitchenObjectParent.ClearKitchenObject(); // clears the previouse clear counter so we can set it
}
this.kitchenObjectParent = kitchenObjectParent;
if (kitchenObjectParent.HasKitchenObject()) // safety check
{
Debug.LogError("IKitchenObjectParent already has kitchenobject");
}
kitchenObjectParent.SetKitchenObject(this);
transform.parent = kitchenObjectParent.GetKitchenObjectFollowTransform();
transform.localPosition = Vector3.zero;
}
public IKitchenObjectParent GetKitchenObjectParent()
{
return kitchenObjectParent;
}
public void DestroySelf()
{
kitchenObjectParent.ClearKitchenObject();
Destroy(gameObject);
}
public static KitchenObject SpawnKitchenObject(KitchenObjectSO kitchenObjectSO, IKitchenObjectParent kitchenObjectParent)
{
Transform kitchenObjectTransform = Instantiate(kitchenObjectSO.prefab);
KitchenObject kitchenObject = kitchenObjectTransform.GetComponent<KitchenObject>();
kitchenObject.SetKitchenObjectParent(kitchenObjectParent);
return kitchenObject;
}
}