luizzak/gpengine
iOS entity-base game framework written in Swift.
Requirements
Xcode 10.2 & Swift 5.0 or higher.
Installation
CocoaPods
GPEngine is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "GPEngine"GPEngine is also available in a Swift package:
Swift Package Manager
GPEngine is also available as a Swift Package
import PackageDescription
let package = Package(
name: "project_name",
dependencies: [
.package(url: "https://github.com/LuizZak/GPEngine.git", from: "4.0.0")
],
targets: []
)License
GPEngine is available under the MIT license. See the LICENSE file for more info.
Serialization
(this optional feature is available under pod 'GPEngine/Serialization'.)
You can use the GameSerializer class to serialize an entity or entire spaces (along with subspaces/entities/components).
This allows you to save partial or complete game states, as well as perform data-driven initialization of game states from pre-made JSON structures.
(see Serialization requirements bellow for info on what's needed to make stuff serializable).
let myProvider = MyTypeProvider() // Implements SerializationTypeProvider
let gameSerializer = GameSerializer(typeProvider: myProvider)
let mySerialized = try gameSerializer.serialize(myEntity)
// .serialize(_:) returns a Serialized object, call .serialize() on it to receive a JSON that you can store:
let json = mySerialized.serialized()
// Store json somewhere...To deserialize the entity back, use the following process:
let json = // Retrieve entity JSON from somewhere...
// Retrieve it back to a Serialized object
// If this fails, that means you got a bad JSON :(
let mySerialized = try Serialized.deserialized(from: json)
// Requires explicit ': Entity' type annotation
let myEntity: Entity = try gameSerializer.extract(from: mySerialized)
// If we reached here, entity was deserialized correctly! Success!Process to serialize/deserialize of Spaces is similar, and uses the same method names.
The serialized JSON returned from Serialized.serialized() follows the given structure:
{
"contentType": "<String - one of the raw values of Serialized.ContentType>",
"typeName": "<String - type name returned by your SerializationTypeProvider to retrieve the class to instantiate back>",
"data": "<Any - this is the JSON returned by the object's serialize() method>"
}Serialized containers can be nested inside one another by adding them to the data field, and retrieved using GameSerializer.extract<T: Serializable>(from: Serialized) method. You must implement custom logic to perform such operations, though.
SerializationTypeProvider
This is a protocol that must be implemented to provide your custom component/subspace types to instantiate during deserialization.
The GameSerializer calls your type provider with the serialized type names, and you must return back a Swift metatype (e.g. MyComponent.self).
The protocol by default implements the method for fetching the serialized name of a type and returns String(describing: Type.self).
A simple type provider can be implemented using an array to store every known serializable type in your game, using a pre-implemented BasicSerializationTypeProvider protocol (provided each serializable ends up taking a unique name matching its type):
class Provider: BasicSerializationTypeProvider {
// Requirement from `BasicSerializationTypeProvider`
var serializableTypes: [(Serializable.Type, (JSON, JsonPath) throws -> Serializable))] = [
(MySerializableComponent.self, MySerializableComponent.init),
(MySerializableSubspace.self, MySerializableSubspace.init),
]
// Now `serializedName(for:)`/`deserialized(from:)` are automatically stubbed using `serializableTypes` array.
}Serialization requirements
To serialize entities and spaces, you need to follow these requirements:
- For entities, every
Componentadded to the entity must implement theSerializableprotocol. - For spaces, every entity must follow the above rule, as well as every subspace also implementing the
Serializableprotocol.
Serializable is a basic protocol for encoding objects using JSON:
/// Describes an object that can be serialized to and back from a JSON object.
/// Implementers of this protocol should take care of guaranteeing that the inner
/// state of the object remains the same when deserializing from a previously
/// serialized object.
public protocol Serializable {
/// Serializes the state of this component into a JSON object.
///
/// - returns: The serialized state for this object.
func serialized() -> JSON
}For decoding, it is expected by BasicSerializationTypeProvider that self.serializableTypes is an array containing the type, along with a reference for the initializer function for that type that will be invoked with the following signature:
/// Initializes an instance of this type from a given serialized state.
///
/// - parameter json: A state that was previously serialized by an instance
/// of this type using `serialized()`
/// - parameter path: The full JSON path to the serialized object. Used for
/// diagnostics purposes.
/// - throws: Any type of error during deserialization.
(_ json: JSON, _ path: JsonPath) throws -> SerializableThe path variable can be provided to various JSON decoding methods for providing context in case a deserialization error is found:
struct MyComponent {
let field: Int
init(json: JSON, path: JsonPath) throws {
field = try json[path: "field"].integer(prefixPath: path)
}
}If deserialization fails, an error is raised with the appropriate full JSON path:
try MyComponent(
json: ["field": true],
path: aPrefixPath
)
// Throws error: "Expected a value of type 'bool' but found a value of type 'int' @ <root>.aPrefixPath.field"To check your entities and spaces are fully serializable, use the GameSerializer.canSerialize(_:) & GameSerializer.diagnoseSerialize(on:) methods on your entities or spaces.
Systems are aimed to be stateless, so they are not supported to be serialized by default. That won't stop you from adding it to your serialization type provider & implementing Serializable protocol on them, however, making them serializable then.
Package Metadata
Repository: luizzak/gpengine
Default branch: master
README: README.md