[swift-users] Checking/getting custom objects from a collection

Milos Rankovic milos at milos-and-slavica.net
Fri Apr 8 10:09:56 CDT 2016


> “If Limb is a Component, then a Spider entity needs eight of them…”
That’s a fair question to ask. 

My intuition is that sporting a `Component` answers a question, “does it have this feature?” In the case of self-propelling creatures, you’d probably ask, “does it have legs”. On the other hand, asking, “does it (still) have a front left leg?” assumes that in general this entity has legs you can ask questions about. All of which would suggest that `SpiderLegs` component would be sufficient for most spider games…

As for the `add` and `remove` methods, I’d probably also return the removed component (if any):

	mutating func remove<T: Component>(_: T.Type) -> T? {
		let c = self.components[T.name]
		self.components[T.name] = nil
		return c as? T
	}

milos

> On 8 Apr 2016, at 15:30, Adriano Ferreira <adriano.ferreira at me.com> wrote:
> 
> Hi Milos,
> 
> Thanks for getting back to me this quickly.
> 
> Well, this part specifically was more of a curiosity based on Jens’ comment:
> 
> “If Limb is a Component, then a Spider entity needs eight of them…”
> 
> I also confess I’m unsure about this… so please don’t bother then.
> 
> Looking forward to hearing your thoughts on the functions though.
> 
> Cheers,
> 
> — A
> 
>> On Apr 8, 2016, at 10:23 AM, Milos Rankovic <milos at milos-and-slavica.net <mailto:milos at milos-and-slavica.net>> wrote:
>> 
>> Hi Adriano,
>> 
>> I’m glad if you are finding this useful. I’ll get back to you on `add` and `remove`, but just let me confirm with you: You actually want to allow multiple, say, `Health` components to be added to your `Character`? Most of the complication in my suggested code comes from trying to prevent that! Things would be simpler if this is not your requirement. I just need to double check that with you, since I can not think of a reason that would be a good thing. I expressly sketched out the implementation so that you can keep `Health` and such immutable, so that you can update the character’s state simply by `add`ing (and therefore replacing) the existing `Health` value.
>> 
>> milos
>> 
>> 
>>> On 8 Apr 2016, at 15:12, Adriano Ferreira <adriano.ferreira at me.com <mailto:adriano.ferreira at me.com>> wrote:
>>> 
>>> Milos,
>>> 
>>> Thanks for taking a look at it, I appreciate you suggestion.
>>> 
>>> It’s protocol-oriented, quite different from the idea I was trying to emulate — the one provided by GameplayKit.
>>> 
>>> Well, I tried it and it works great.
>>> 
>>> 
>>> Now, would you implement those methods differently?
>>> 
>>> mutating func add<T: Component>(component: T) {
>>>     self.components[T.name] = component
>>> }
>>> 
>>> mutating func remove<T: Component>(_: T.Type) {
>>>     self.components[T.name] = nil
>>> }
>>> 
>>> Also, since the key to the components dictionary is the name, adding a component of the same type will replace the exiting one.
>>> 
>>> How would you change that so it would be possible to add some components more than once, as Jens mentioned?
>>> 
>>> Best,
>>> 
>>> —A
>>> 
>>>> On Apr 8, 2016, at 7:12 AM, Milos Rankovic <milos at milos-and-slavica.net <mailto:milos at milos-and-slavica.net>> wrote:
>>>> 
>>>> A type-uniquing alternative (see my previous message):
>>>> 
>>>> // Swift 2.2
>>>> 
>>>> // Entity-Component System (sketch):
>>>> protocol Component {
>>>> 	static var name: String { get } // not necessary (see comments below)
>>>> }
>>>> 
>>>> extension Component {
>>>> 	// can be overridden with `let` by conforming types
>>>> 	static var name: String { return String(self.dynamicType) }
>>>> }
>>>> 
>>>> protocol Entity {
>>>> 	static func with(_: Component...) -> Self
>>>> 	var components: [String:Component] { get set }
>>>> 	init()
>>>> 	func component<T: Component>(_: T.Type) -> T?
>>>> }
>>>> 
>>>> extension Entity {
>>>> 	static func with(components: Component...) -> Self {
>>>> 		var d: [String:Component] = [:]
>>>> 		for c in components { d[c.dynamicType.name/* String(c.dynamicType) */] = c }
>>>> 		var entity = self.init()
>>>> 		entity.components = d
>>>> 		return entity
>>>> 	}
>>>> 	func component<T: Component>(_: T.Type) -> T? {
>>>> 		return self.components[T.name/* String(T) */] as? T
>>>> 	}
>>>> 	// TODO: mutating func add<T: Component>(_: T)
>>>> 	// TODO: mutating func remove<T: Component>(_: T.Type)
>>>> }
>>>> 
>>>> // game:
>>>> struct Character: Entity {
>>>> 	var components: [String:Component] = [:]
>>>> }
>>>> 
>>>> struct Health: Component {
>>>> 	var percent = 100.0
>>>> 	var dead: Bool { return percent <= 0 }
>>>> }
>>>> 
>>>> struct Attack: Component {
>>>> 	var range = 0, damage = 0
>>>> }
>>>> 
>>>> // use:
>>>> let health = Health()
>>>> let attack = Attack()
>>>> 
>>>> let character = Character.with(health, attack)
>>>> 
>>>> character.component(Health)?.percent // 100
>>>> 
>>>> milos
>>>> 
>>>>> On 8 Apr 2016, at 11:05, Milos Rankovic via swift-users <swift-users at swift.org <mailto:swift-users at swift.org>> wrote:
>>>>> 
>>>>> This is just a sketch. There may be issues down the line (I’ve indicated some with `TODO`s), but it works and you can try it in the playground:
>>>>> 
>>>>> // Swift 2.2
>>>>> 
>>>>> // utility:
>>>>> extension Array {
>>>>> 	func first <T> (_: T.Type) -> T? {
>>>>> 		for e in self where e is T { return e as? T }
>>>>> 		return nil
>>>>> 	}
>>>>> }
>>>>> 
>>>>> // Entity-Component System (sketch):
>>>>> protocol Component { }
>>>>> 
>>>>> protocol Entity {
>>>>> 	static func with(_: Component...) -> Self
>>>>> 	// TODO: make get only
>>>>> 	// also should be a set-by-type really, but that would 
>>>>> 	// force `Component` to be a class (which may be worth it)
>>>>> 	var components: [Component] { get set } 
>>>>> 	init()
>>>>> 	func component<T: Component>(_: T.Type) -> T?
>>>>> }
>>>>> 
>>>>> extension Entity {
>>>>> 	static func with(components: Component...) -> Self {
>>>>> 		var entity = self.init()
>>>>> 		// TODO: enforce uniquely typed elements
>>>>> 		entity.components = components
>>>>> 		return entity
>>>>> 	}
>>>>> 	func component<T: Component>(_: T.Type) -> T? {
>>>>> 		return self.components.first(T)
>>>>> 	}
>>>>> }
>>>>> 
>>>>> // game:
>>>>> struct Character: Entity {
>>>>> 	// TODO: make private
>>>>> 	var components: [Component] = [] 
>>>>> }
>>>>> 
>>>>> struct Health: Component {
>>>>> 	var percent = 100.0
>>>>> 	var dead = false
>>>>> }
>>>>> 
>>>>> struct Attack: Component {
>>>>> 	var range = 0, damage = 0
>>>>> }
>>>>> 
>>>>> // use:
>>>>> var health = Health()
>>>>> var attack = Attack()
>>>>> 
>>>>> var character = Character.with(health, attack)
>>>>> 
>>>>> character.component(Health)?.percent // 100
>>>>> 
>>>>> Hope this helps,
>>>>> 
>>>>> milos
>>>>> 
>>>>>> On 8 Apr 2016, at 00:47, Adriano Ferreira via swift-users <swift-users at swift.org <mailto:swift-users at swift.org>> wrote:
>>>>>> 
>>>>>> Hi everyone!
>>>>>> 
>>>>>> I’m experimenting with Entity-Component Systems <https://en.wikipedia.org/wiki/Entity_component_system> and I’d appreciate if you could help me working on how to check/get custom objects from a collection.
>>>>>> 
>>>>>> The idea is to verify if an entity contains a particular component and, if so, retrieve it.
>>>>>> 
>>>>>> Here’s the API I’d like work on:
>>>>>> 
>>>>>> 
>>>>>> // Entity Library
>>>>>> class Character: Entity {}
>>>>>> 
>>>>>> // Component Library
>>>>>> class HealthComponent: Component {
>>>>>>     var health = 100.0
>>>>>>     var isDead = false
>>>>>> }
>>>>>> 
>>>>>> class AttackComponent: Component {
>>>>>>     var range = 0
>>>>>>     var damage = 0
>>>>>> }
>>>>>> 
>>>>>> // Usage
>>>>>> var healthComponent = HealthComponent()
>>>>>> var attackComponent = AttackComponent()
>>>>>> 
>>>>>> var components: [ComponentType] = [healthComponent, attackComponent]
>>>>>> var char = Character(components: components)
>>>>>> 
>>>>>> let hc = char.get(component: HealthComponent)
>>>>>> let ac = char.get(component: AttackComponent)
>>>>>> 
>>>>>> 
>>>>>> So, what are your thoughts on the TODOs below?
>>>>>> 
>>>>>>>>>>>> 
>>>>>> import Foundation
>>>>>> 
>>>>>> protocol ComponentType {
>>>>>>     var entity: EntityType? { get }
>>>>>> }
>>>>>> 
>>>>>> protocol EntityType {
>>>>>>     var components: [ComponentType] { get }
>>>>>>     func get<T: ComponentType>(component c: T.Type) -> T?
>>>>>>     func add(component c: ComponentType)
>>>>>>     func remove(component c: ComponentType)
>>>>>> }
>>>>>> 
>>>>>> class Component: ComponentType {
>>>>>>     var entity: EntityType?
>>>>>> }
>>>>>> 
>>>>>> class Entity: EntityType {
>>>>>>     var components = [ComponentType]()
>>>>>> 
>>>>>>     init(components: [ComponentType]) {
>>>>>>         for component in components {
>>>>>>             self.add(component: component)
>>>>>>         }
>>>>>>     }
>>>>>> 
>>>>>>     func get<T: ComponentType>(component c: T.Type) -> T? {
>>>>>>         // TODO: - not sure how to work the types here
>>>>>>         // if `self` contains component of given type, return it
>>>>>>         // otherwise, return nil
>>>>>>     }
>>>>>> 
>>>>>>     func add(component c: ComponentType) {
>>>>>>         // TODO: - depends on the `get` function
>>>>>>         // if `self` already contains component, just return
>>>>>>         // otherwise, self.components += [component]
>>>>>>     }
>>>>>> 
>>>>>>     func remove(component c: ComponentType) {
>>>>>>         // TODO: - also depends on the `get` function
>>>>>>         // if `self` contains component, remove it
>>>>>>         // otherwise, just return
>>>>>>     }
>>>>>> }
>>>>>> 
>>>>>> 
>>>>>> 
>>>>>> Best,
>>>>>> 
>>>>>> —A
>>>>>> _______________________________________________
>>>>>> swift-users mailing list
>>>>>> swift-users at swift.org <mailto:swift-users at swift.org>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-users <https://lists.swift.org/mailman/listinfo/swift-users>
>>>>> 
>>>>> _______________________________________________
>>>>> swift-users mailing list
>>>>> swift-users at swift.org <mailto:swift-users at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-users <https://lists.swift.org/mailman/listinfo/swift-users>
>>>> 
>>> 
>> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20160408/0cacf4c1/attachment.html>


More information about the swift-users mailing list