5
votes

I'm diving into GameplayKit with Spritekit and from what I gather, you subclass GKEntity and then start adding GKComponents to that entity. The entity would more or less just be a bag of components that fill some function.

The part I'm confused about is communication between components. How do you keep them decoupled. For example, lets say I have a HealthComponent class, and I add that component to a PlayerEntity, and an EnemyEntity. I also have a HealthBarComponent but I only want a health bar to appear above the player. When the player takes damage, that information needs to be updated in the HealthBarComponent.

So how should that information be sent? I see there is a class called GKComponentSystem in the documentation. I'm not 100% on how that should be used.

Another question is.. when the player's health reaches zero he should regenerate while the enemy should stay dead. When the player runs out of lives, the game ends.

The health system for both an enemy and player would be roughly the same, but the events around death would be totally different for each. I'm not following how to use a component system while keeping the unique behavior of each entity.

some pseudo-code would be great

3
on the bottom here they talk about communication between components. seems like theres no specific way to do it perfectly. Some coupling might be necessary. gameprogrammingpatterns.com/component.htmlhamobi
You may find the DemoBots sample project interesting. The game's equivalent of health is charge, for which there is a ChargeComponent which notifies its delegate when charge is lost. The game also has a similar mechanic to the health regeneration one you mentioned, which was implemented using a GKStateMachine and accompanying states.ABakerSmith

3 Answers

5
votes

It looks like this framework works a bit differently than others I've seen, in that systems only work on a single type of component rather than on entities with groups of component types.

In the GamePlay-Kit framework you can either loop through and update your entities manually (which in turns updates each entities components) or create a class which inherits from GKComponentSystem. Then when you update the system it updates all the components you have added to it so long as their class type matches the type you initialized the system with

To handle your health bar issue I would say make a HealthBarComponent which, during its update, retrieves the HealthComponent from its owner entity, reads the health value & renders itself every frame. But you only add a HealthBarComponent to your player entity.

You can retrieve the owner entity from a component and vice versa (see GKComponent.entity & GKEntity.components) so you could update your health bar like this:

/*Note: I'm not an ios developer, this is pseudocode*/

HealthBarComponent.update(){
  int healthValue = self.entity.components.get(HealthComponent).value;
  //Now render your health bar accordingly or hold onto this for later
}

To handle the player death issue I would think your best bet would be to have two different types of Health components (PlayerHealth & EnemyHealth) with two different corresponding systems. That or have a single Health component but 2 separate 'Death' components. Yeah it seems redundant but I'm having a hard time thinking of a better way inside of this framework. In my experience you either spend your time worrying about keeping everything completely decoupled and reusable or you build a game :)

3
votes

Components can access their entities via the self.entity property. From there, you can query other components to pass data to via the entity componentForClass property:

guard let moveComponent = entity?.componentForClass(MoveComponent.self) else {
        fatalError("A MovementComponent's entity must have a MoveComponent")
    }

The iOS Games by Tutorials book has an excellent tutorial that covers GameplayKit entities and components.

0
votes

Notifications are nice for this kind of problem. There's no direct object coupling at all, and it's very extensible if more than one object ends up needing to know (e.g. your health bar component, a higher-level game object that determines game over, maybe even enemy/ally AI to behave differently when health is low).

You could have a notification named "playerHealthChanged" and have your health-bar component and other objects register independently to respond to that event. (You likely need to let the HealthComponent instance know whether it should post this notification, so only the player posts - perhaps it could use isKind(of:) on its entity, or just have a bool field to enable posting, set to true for the player instance).

I usually put all notification name definitions in a single module so any class can access them:

let kPlayerHealthChangedNotification = Notification.Name("playerHealthChanged")

Here's the way the component would post the notification (you could pass an object other than self if desired):

NotificationCenter.default.post(name: kPlayerHealthChangedNotification, object:self)

Then the objects that care about player health changes can register like so in their init code - create a handler function, than add self as an observer for the notification:

@objc func onPlayerHealthChanged(_ notification:Notification) {
  // do whatever is needed; notification.object 
  // has the object from the post
}

// put this in a setup method - init or similar:
  NotificationCenter.default.addObserver(self,
      selector: #selector(onPlayerHealthChanged(_:)),
      name: kPlayerHealthChangedNotification
      object:nil)

Here's the documentation: https://developer.apple.com/documentation/foundation/notifications