Pickup System

Project Information

  1. Development Environment & Team
  2. Idea
  3. Usage
  4. Showcase
  5. Post Mortem

Code Snippets

  1. Pickup.cs
  2. PickupChain.cs
  3. PickupSystem.cs

Project Information

The project we worked on is called Operation Starfall (working title), and its a 2.5D couch co-op metroidvania game with a 80's cartoons theme. this is a massive project that multiple developers have and will work on in the future. This game will eventually be a commercial product. i wasnt working on the feature at the start but joined in later on so i originally didnt work on this feature on this feature/user story i worked with 2 other devs to make this. Here are links to their portfolio pages on this feature: Bas de reus Patricia Kuipers

The idea was to have pickups in the level that you could pickup, and have 2 different types of chains. 1 ordered where you have to pick up items in a certain order and 1 unordered where you have to pick up certain items but not in a specified order

The PickupSystem file allows you to assign events for level designers, and keeps track of pickups. you can create new chains in the inspector and assigned the pickups you want. then also check if you want them to be ordered or not. Here you can see 2 screenshots of what it looks like in the inspector

We first had a very sloppy way of doing everyting with code but then made everything more DRY, more abstract and applied SRP. eventually it was done and afterwards Patricia added more functionality to the pickups allowing them to change transparency depending on if they are next to be pickup in a ordered chain

Code Snippets

This script is just for the pickup. nothing more has to be said

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public class Pickup : MonoBehaviour { [SerializeField] private GameObject visual; [HideInInspector] [CanBeNull] public PickupChain parentChain; private bool _isPickedUp; public bool IsPickedUp { get => _isPickedUp; set { _isPickedUp = value; SetActivation(_isPickedUp); } } public void PickUp() => PickupSystem.Instance.AddPickup(this); private void SetActivation(bool isPickedUp) => visual.SetActive(!isPickedUp); public bool HasChain() { return parentChain.HasPickups(); } }

This script basicly is a array with events tied to it, it contains a array of pickups, a event that is triggered when the chain is complete and a bunch of methods

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

[Serializable] public class PickupChain { [SerializeField] private bool isOrdered; public UnityEvent onChainComplete = new UnityEvent(); public Pickup[] pickups = new Pickup[] {}; private int _nextItemIndex; public void Init() { var lenght = pickups.Length; for (int i = 0; i < lenght; i++) { pickups[i].parentChain = this; } if (isOrdered) SetPickupsTransparent(); } public void AddPickup(Pickup targetPickup) { targetPickup.IsPickedUp = isOrdered ? AddOrdered(targetPickup) : AddUnordered(); if (!IsComplete) return; onChainComplete?.Invoke(); } private bool AddUnordered() { _nextItemIndex++; return true; } private bool AddOrdered(Pickup pickup) { var index = Array.IndexOf(pickups, pickup); if (index == _nextItemIndex) { _nextItemIndex++; SetNextPickOpaque(); return true; } return false; } public bool HasPickups() { return !pickups.IsEmpty(); } private void SetPickupsTransparent() { for (int i = 0; i < pickups.Length; i++) { if (i != _nextItemIndex) pickups[i].GetComponent<PickupEffects>().SetVisualTransparent(true); } } private void SetNextPickOpaque() { if(_nextItemIndex == pickups.Length) return; pickups[_nextItemIndex].GetComponentInChildren<PickupEffects>().SetVisualTransparent(false); } public bool IsComplete => _nextItemIndex == pickups.Length; }

This is the script that holds multiple chains and keeps track of them, also has some events and methods checking if those events should be triggered

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

public class PickupSystem : GenericSingleton<PickupSystem> { private bool _allChainsComplete; private bool _everythingIsCollected; [Header("Events")] [SerializeField] private UnityEvent onEverythingComplete = new UnityEvent(); [SerializeField] private UnityEvent onAllSeparatePickupsCollected = new UnityEvent(); [SerializeField] private UnityEvent onAllChainsComplete = new UnityEvent(); [Header("Lists")] [SerializeField] private List<Pickup> allInteractiveItems; [SerializeField] private List<Pickup> separateInteractiveItems; [SerializeField] private List<PickupChain> chains; private void Start() { InitializeChains(); FindAllPickups(); } private void InitializeChains() { var length = chains.Count; if (length == 0) return; for (int i = 0; i < length; i++) { chains[i].onChainComplete.AddListener(OnChainCompleted); //Zie je niet in inspector chains[i].Init(); } } private void OnChainCompleted() { CheckAllCollected(); CheckAllChainsDone(); } private void FindAllPickups() { var allInteractivePickups = FindObjectsOfType<Pickup>(); var length = allInteractivePickups.Length; for (int i = 0; i < length; i++) { var currentPickup = allInteractivePickups[i]; allInteractiveItems.Add(currentPickup); if (!currentPickup.HasChain()) separateInteractiveItems.Add(currentPickup); } } public void AddPickup(Pickup targetPickup) { if(targetPickup.IsPickedUp) return; if (targetPickup.parentChain.HasPickups()) { targetPickup.parentChain.AddPickup(targetPickup); return; } targetPickup.IsPickedUp = true; if (!separateInteractiveItems.All(item => item.IsPickedUp)) return; onAllSeparatePickupsCollected?.Invoke(); CheckAllCollected(); } private void CheckAllChainsDone() { if (!chains.All(chain => chain.IsComplete)) return; _allChainsComplete = true; onAllChainsComplete?.Invoke(); } private void CheckAllCollected() { if (!_everythingIsCollected && _allChainsComplete && allInteractiveItems.All(item => item.IsPickedUp)) { _everythingIsCollected = true; onEverythingComplete?.Invoke(); } } }