I'm using state pattern for my unity game. I have three states; HappyState, SurprisedState and SadState. HappyState is default state. I want the character to jump and enter surprised state by clicking left-mouse. Entering surprised state, a few jobs need to be done which I defined as a void to be subscribed to an event, But it doesn't work!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
public class CapsuleScript : MonoBehaviour
{
public event Action SliderAction;
public Slider slider;
public Text stateText;
public GameObject hands;
public Rigidbody rb;
public CapsuleBasicStates currentstate;
public readonly HappyState happyState = new HappyState();
public readonly SadState sadState = new SadState();
public readonly SurprisedState surprisedState = new SurprisedState();
public SpriteRenderer renderer;
public Sprite happysprite, sadsprite, surprisedsprite;
// Start is called before the first frame update
void Start()
{
slider.value = 0;
renderer = GetComponentInChildren<SpriteRenderer>();
rb = GetComponent<Rigidbody>();
TransitionToState(happyState);
}
public void OnCollisionEnter(Collision other) {
currentstate.OnCollisionEnter(this);
}
// Update is called once per frame
void Update()
{
currentstate.Update(this);
}
public void SetSprite(Sprite sprite)
{
renderer.sprite = sprite;
}
public void TransitionToState(CapsuleBasicStates state)
{
currentstate = state;
currentstate.EnterState(this);
}
public IEnumerator SliderHandler()
{
yield return new WaitForSeconds(1f);
slider.value +=1;
StartCoroutine(SliderHandler());
}
public void IEHandler()
{
StartCoroutine(SliderHandler());
}
}
Here is SurprisedState script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SurprisedState : CapsuleBasicStates
{
public override void EnterState(CapsuleScript player)
{
player.SetSprite(player.surprisedsprite);
player.stateText.text = player.surprisedState.ToString();
player.slider.gameObject.SetActive(true);
player.SliderAction += player.IEHandler;
}
public override void OnCollisionEnter(CapsuleScript player)
{
//player.TransitionToState(player.happyState);
}
public override void Update(CapsuleScript player)
{
if(player.slider.value ==20)
{
player.SliderAction -= player.IEHandler;
player.TransitionToState(player.happyState);
}
}
}
HappyState script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HappyState : CapsuleBasicStates
{
public override void EnterState(CapsuleScript player)
{
player.SetSprite(player.happysprite);
player.stateText.text = player.happyState.ToString();
player.hands.SetActive(false);
}
public override void OnCollisionEnter(CapsuleScript player)
{
}
public override void Update(CapsuleScript player)
{
if(Input.GetButton("Fire1"))
{
player.rb.AddForce(Vector3.up * 150f);
player.TransitionToState(player.surprisedState);
}
if(Input.GetButton("Fire2"))
{
player.rb.AddForce(Vector3.up *250f);
player.TransitionToState(player.sadState);
}
}
}
Few points before I post my solution:
Try to post all scripts related to the question you are asking.
Comment your code or try to explain what each part of your script is doing.
BaseSate.cs
[System.Serializable]
public abstract class BaseState
{
public enum PlayerStates { HappyState = 0, SurprisedState = 1 }
public abstract PlayerStates PlayerState { get; }
public abstract void EnterState(PlayerScript playerScript);
public abstract void UpdateState();
public abstract void ExitState();
}
HappySate.cs
using UnityEngine;
[System.Serializable]
public class HappyState : BaseState
{
public override PlayerStates PlayerState => PlayerStates.HappyState;
private PlayerScript _playerScript;
public override void EnterState(PlayerScript playerScript)
{
_playerScript = playerScript;
Debug.Log($"Entered {PlayerState}");
}
public override void UpdateState()
{
if (Input.GetButtonDown("Fire1"))
{
_playerScript.ChangeState(new SurprisedState());
}
}
public override void ExitState()
{
Debug.Log($"Exited {PlayerState}");
}
}
SurprisedState.cs
using UnityEngine;
[System.Serializable]
public class SurprisedState : BaseState
{
public override PlayerStates PlayerState => PlayerStates.SurprisedState;
private PlayerScript _playerScript;
public override void EnterState(PlayerScript playerScript)
{
_playerScript = playerScript;
// subscribe to sliderAction on enter
playerScript.playerSliderAction += OnSliderChange;
Debug.Log($"Entered {PlayerState}");
}
private void OnSliderChange(float sliderValue)
{
// use Mathf.Approximately instead of == when comparing
// floating numbers.
if (Mathf.Approximately(sliderValue, 20f))
{
_playerScript.ChangeState(new HappyState());
}
}
public override void UpdateState() { }
public override void ExitState()
{
// unSubscribe to sliderAction on exit
_playerScript.playerSliderAction -= OnSliderChange;
Debug.Log($"Exited {PlayerState}");
}
}
PlayerScript.cs
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class PlayerScript : MonoBehaviour
{
[SerializeField]
private SpriteRenderer playerRenderer;
[SerializeField]
private Sprite[] playerStateSprites;
[SerializeField]
private Slider playerSlider;
public UnityAction<float> playerSliderAction;
private BaseState _currentState;
private void Awake()
{
Initialize();
}
private void Update()
{
_currentState.UpdateState();
}
private void Initialize()
{
ChangeState(new HappyState());
// use slider onValueChanged instead of checking slider
// value every frame.
// invoke unity action when the value has been changed.
playerSlider.onValueChanged.AddListener(sliderValue =>
{
playerSliderAction?.Invoke(sliderValue);
});
}
public void ChangeState(BaseState newState)
{
// change state only when it is different
// from previous state.
if (_currentState == newState)
{
return;
}
_currentState?.ExitState();
_currentState = newState;
_currentState.EnterState(this);
playerRenderer.sprite = playerStateSprites[(int)newState.PlayerState];
Debug.Log($"Current State : {_currentState.PlayerState}");
}
}
PlayerScript inspector view
Execution Overview gif:
https://gfycat.com/grimflowerycollardlizard
Related
So i made a starter game from brackeys and i get 3 errors please help me
Script:GameManager
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public bool gameHasEnded = false;
public float restartDelay = 1f;
public void CompleteLevel ()
{
Debug.Log("LEVEL COMPLETE");
}
public void EndGame ()
{
if (gameHasEnded == false)
{
gameHasEnded = true;
Debug.Log("GAME OVER");
Invoke("Restart", restartDelay);
}
}
void Restart ()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
script:EndTriger
using UnityEngine;
public class EndTriger : MonoBehaviour
public GameManager gameManager;
{
void OnTriggerEnter()
{
gameManager.CompleteLevel();
}
}
EndTriger:
using UnityEngine;
public class EndTriger : MonoBehaviour
{
public GameManager gameManager;
void OnTriggerEnter()
{
gameManager.CompleteLevel();
}
}
I have implemented the state pattern for my game in Unity.
Thus I have a main class (BattleSystem) which starts by calling different states.
One of those states is the AttackState. The AttackState has an attackButton which is an UI Button (using UnityEngine.UI).
My State class looks like this:
public abstract class State : MonoBehaviour
{
protected BattleSystem battleSystem;
public abstract void Tick();
public virtual void OnStateEnter() { }
public virtual void OnStateExit() { }
public State(BattleSystem battleSystem) {
this.battleSystem = battleSystem;
}
}
My StateMachine class looks like this:
public abstract class StateMachine : MonoBehaviour
{
protected State state;
public void SetState(State state) {
this.state = state;
state.OnStateEnter();
}
}
The Button is in the BattleSystem class like this:
public class BattleSystem : StateMachine
{
public Button attackButton;
// Start is called before the first frame update
void Start()
{
SetState(new AttackState(this));
}
I dragged the AttackButton object onto my attackButton field in the inspector in the Unity editor.
My AttackState.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AttackState : State
{
public AttackState(BattleSystem battleSystem) : base(battleSystem)
{
}
public override void Tick()
{
}
public override void OnStateEnter()
{
Debug.Log("AttackState");
battleSystem.attackButton.onClick.AddListener(delegate () { this.Attack(); });
Debug.Log(battleSystem.attackButton.gameObject.name);
}
public void Attack() {
Debug.Log("Attacking");
foreach (Transform child in battleSystem.cardDropZone.transform)
{
Card card = (Card)child.GetComponent("Card");
// inflict damage to opponent equal to attack of cards
if (battleSystem.canDamageBeInflicted(card))
{
battleSystem.inflictDamage(card);
// adjust crystalCount
TurnUtilities.crystalCount -= card.crystalCost;
// Move card to graveyard after it has been used
battleSystem.sendToGraveyard(child);
battleSystem.attackButton.GetComponentInChildren<Text>().text = "End Turn";
}
else
{
Debug.Log("not enough crystals");
}
}
if (PlayerIsDead())
{
battleSystem.isPlayerDead = true;
battleSystem.SetState(new GameEndingState(battleSystem));
}
if (EnemyIsDead())
{
battleSystem.isEnemyDead = true;
battleSystem.SetState(new GameEndingState(battleSystem));
}
}
bool PlayerIsDead()
{
if (battleSystem.playerHealthbar.value <= 0)
{
return true;
}
return false;
}
bool EnemyIsDead()
{
if (battleSystem.enemyHealthbar.value <= 0)
{
return true;
}
return false;
}
}
The funny thing is that the Debug.Log(battleSystem.attackButton.gameObject.name); works and gives me the name of the GameObject. The event is not being registered though. What did I miss?
Picture of my Scene:
EDIT: I found out that this code works:
btn.onClick.AddListener(() => Debug.Log("Test"));
Putting the method inside does not work? What is happening here :D
Something really odd happens when I'm trying to load a scene.
The code that generates an error (I guess):
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class playSc : MonoBehaviour {
public string levelToLoad;
public Animator menumusic;
public GameObject background;
public GameObject text;
public GameObject progressSlider;
public Text progressText;
public Slider progressBar;
private int loadProgress = 0;
public void close()
{
Application.Quit();
}
public void play()
{
menumusic.SetBool("FadeOUT", true);
GameObject.Find("Clock").SetActive(false);
progressText.text = "Think about the Universe to continue.\nThinking progress:";
StartCoroutine(DisplayLoadingScreen("Game"));
}
public void credits()
{
menumusic.SetBool("FadeOUT", true);
GameObject.Find("Clock").SetActive(false);
progressText.text = "Think about your behaviour.\nThinking progress:";
StartCoroutine(DisplayLoadingScreen("Credits"));
}
IEnumerator DisplayLoadingScreen(string level)
{
background.SetActive(true);
text.SetActive(true);
progressSlider.SetActive(true);
AsyncOperation async = SceneManager.LoadSceneAsync(level);
while (!async.isDone)
{
loadProgress = (int)(async.progress * 100);
progressBar.value = loadProgress;
yield return null;
}
}
}
I tried to use Application.LoadLevel() and it still is not working. It was working with this code but at one moment it stopped.
I need to call updateGold() in ShowMoney class but failed.
If success,it should show money.gold in console, but don't show anything.
And golText.text don't update.
Even I change to Debug.Log("OK") in updateGold(),don't show anything in console.
But when I call updateGold() in Money class, it success. What the difference?
=========================================================
//Prop.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Prop : MonoBehaviour,IPointerClickHandler
{
public int goldPrice;
public int diamondPrice;
public int propID;
public GameObject goldError;
public GameObject diamondError;
public GameObject purchasePanel;
//check if purchased when back to this Level
void Awake()
{
if(Bag.propIsPurchased[propID]==true)
{
alreadySold();
}
}
//show purchase panel and add delegate
public void OnPointerClick (PointerEventData eventData)
{
purchasePanel.SetActive(true);
ConfirmPurchase.ensureOperation += this.purchase;
CancelPurchase.cancelOperation += this.cancel;
}
public void purchase()
{
if (goldPrice>0)
{
if(goldPrice > Money.gold)
{
goldError.SetActive(true);
}
else
{
Money.payGold(goldPrice);
ShowMoney.updateGold();//problem here
Bag.addProp(propID);
alreadySold();
}
}
//...
}
//...
//ShowMoney.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class ShowMoney : MonoBehaviour
{
public GameObject goldText;
public GameObject diamondText;
public static Text golText;
public static Text diaText;
void Awake()
{
golText = goldText.GetComponent<Text>();
diaText = diamondText.GetComponent<Text>();
updateGold();
updateDiamond();
}
public static void updateGold()
{
Debug.Log(Money.gold);
golText.text = Money.gold.ToString();
}
public static void updateDiamond()
{
diaText.text = Money.diamond.ToString();
}
}
//Money.cs
using UnityEngine;
using System.Collections;
public class Money: MonoBehaviour
{
public static int gold = 100;
public static int diamond = 20;
public static Money instance;
void Awake()
{
instance = this;
}
public static void earnGold(int gol)
{
gold += gol;
}
public static void earnDiamond(int dia)
{
diamond += dia;
}
public static void payGold(int gol)
{
gold -= gol;
//ShowMoney.updateGold();//can work here
}
public static void payDiamond(int dia)
{
diamond -= dia;
}
}
Define your method Before Void and still Problem not solved then Make Protected Method that only accessible by its next method .
public static void updateGold()
{
Debug.Log(Money.gold);
golText.text = Money.gold.ToString();
}
public static void updateDiamond()
{
diaText.text = Money.diamond.ToString();
}
public void Awake()
{
golText = goldText.GetComponent<Text>();
diaText = diamondText.GetComponent<Text>();
updateGold();
updateDiamond();
}
I want to change the playerCurScore integer (in ScoreManager script) from HarmEnemiesScript.
(Script attached to Object in Scene)
using UnityEngine;
using System.Collections;
public class ScoreManager : MonoBehaviour
{
public int playerScore;
public int playerCurScore;
public void Start()
{
playerScore = 0;
playerCurScore = 0;
}
public void Update()
{
playerCurScore = playerScore;
}
}
(Script attached on Enemy)
using UnityEngine;
using System.Collections;
public class HarmEnemies : MonoBehaviour
{
public float enemyHealth;
public float enemyCurHealth;
public float playerDamage;
public void Start()
{
enemyCurHealth = enemyHealth;
}
public void OnTriggerEnter(Collider theCollision)
{
if(theCollision.tag == "Fireball")
{
enemyCurHealth = enemyCurHealth - playerDamage;
Destroy (theCollision);
}
if(enemyCurHealth <= 0)
{
Destroy (this.gameObject);
}
}
}
So how can I change the playerCurScore Int from HarmEnemies. I know that I have to use GetComponent and I can use playerCurScore++;
You are making several mistakes. The main one is that the ScoreManager script should not be attached to the EnemyGameObject.
Make a separate GameObject and name it ScoreManager. Then find it in the scene and update the score.
ScoreManager s = GameObject.Find("ScoreManager");
s.SendMessage("incrementScore", amount);
In the ScoreManager class you need this:
private static int score;
public void incrementScore(int amount)
{
score += amount;
}
If you are clever you make the whole ScoreManager class static and then it wouldn't even have to be a MonoBehaviour.
You would then call it like this:
ScoreManager.incrementScore(amount);
Information about static classes.
I could fix it now, thank you for the static hint. my code looks like this now:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class ScoreManager : MonoBehaviour
{
public static float playerScore;
public Text scoreCount;
public void Update()
{
ScoreController ();
}
public void ScoreController()
{
scoreCount.text = "Score: " + playerScore;
Debug.Log ("Score: " + playerScore);
}
}
using UnityEngine;
using System.Collections;
public class HarmEnemies : MonoBehaviour
{
public float enemyHealth;
public float enemyCurHealth;
public float playerDamage;
public void Start()
{
enemyCurHealth = enemyHealth;
}
public void OnTriggerEnter(Collider theCollision)
{
if(theCollision.tag == "Fireball")
{
enemyCurHealth = enemyCurHealth - playerDamage;
Destroy (theCollision);
}
if(enemyCurHealth <= 0)
{
ScoreManager.playerScore = (ScoreManager.playerScore + 1);
Destroy (this.gameObject);
}
}
}