Dynamic Object Creation in Objective-C
- Yi Yin
- Apr 23, 2018
- 3 min read
Updated: May 2, 2018
This is the first one of the series of articles on technical tips of game engine development. It comes from my experience making the engine for our award-winning games, the Ballade series. Since they are iOS games, the description will be majorly in Objective-C, and sometimes other scripting languages.
Dynamic Object Creation, is the process that creates an object whose type remains unknown until runtime. It is very useful in game programming, or where we need to add different types of new objects irregularly. Dynamic Object Creation usually goes with the "Factory" design pattern, giving your program both elegance and flexibility.
Problem
Assume you are using a scripting language - let's say Lua - to control the creation of different game objects, whose corresponding classes are written in Objective-C. At first, you have only one type of game objects to create: grass. So you implemented that in Objective-C:
[Objective-C]
@interface Grass
// ...
@end
You hope you can use Lua code in your script files to create a grass asset at (50,50) like this:
[Lua]
game.createGrass(50,50)
Therefore, you have to implement a "bridge" function (Lua => Objective-C):
[Objective-C]
-(Grass*)createGrassAt:(int)x y:(int)y;
Very well. With the bridge function and Lua engine, each time you call game.createGrass(x,y) in Lua, it is then forwarded to function -(Tree*)createGrassAt:(int)x y:(int)y in Objective-C. Everything looks so good so far, until you decide to add another 2 types of game objects: sand and blueberry. Now the Objective-C files look like this:
// Grass.h & Grass.m
@interface Grass
// ...
@end
// Sand.h & Sand.m
@interface Sand
// ...
@end
// Blueberry.h & Blueberry.m
@interface Blueberry
// ...
@end
And you want to use them in your Lua files like this:
[Lua]
game.createGrass(100,200)
game.createSand(300,50)
Or even batch-create a lot of game objects (e.g. blueberries because you REALLY love them):
[Lua]
local pos_all_berries={ {x=10,y=10}, {x=50,y=50},{x=100,y=100} --[[and many others]]-- }
for i,pos in ipairs(pos_all_berries) do --in this for loop, i is the index, and pos is pos_all_berries[i]
game.createBlueberry(pos.x, pos.y)
end
Therefore, you have to write 3 bridge functions (Lua => Objective-C):
[Objective-C]
-(Grass*)createGrassAt:(int)x y:(int)y;
-(Sand*)createSandAt:(int)x y:(int)y;
-(Blueberry*)createBlueberryAt:(int)x y:(int)y;
Very well. You feel satisfied about all those functions...until your boss tells you that there are 20 types of game objects for you: grass, bush, tree, forest, woodlands, sand, pebble, stone, rock, boulder, blueberry, strawberry, apple, watermelon, pumpkin, ..., etc. Now what will you do? Writing 20 bridge functions?
Analysis
Actually you would hope you can do something like this:
[Lua]
game.createObject("Tree", {x=50,y=50});
game.createObject("Stone", {x=100,y=100});
--and many other game objects
Or even:
[Lua]
local objects=[
{type="Tree", args={x=50,y=50}},
{type="Stone", args={x=100,y=100}}
--and many other game objects
]
for i, entry in ipairs(objects) do --same as previously, entry will be objects[i] in this for-loop
game.createObject(entry.type, entry.args)
end
[Objective-C]
-(GameObject*)createObject:(NSString*)type with:(NSDictionary*)args{
}
Solution
Now here's the solution: Dynamic Object Creation. In Objective-C, you can use an NSString object (as the class' name) to create the class' instance:
[Objective-C]
Class cls=NSClassFromString("Tree");
assert(cls);
[[cls alloc] init];
Therefore, we can redesign the Objective-C classes of all game objects like this:
[Objective-C]
@interface Tree
-(instancetype)initWithArgs:(NSDictionary*)args;
//...
@end
@interface Stone
-(instancetype)initWithArgs:(NSDictionary*)args;
//...
@end
// and so on. Just make sure all game object classes should implement -(instancetype)initWithArgs:args
Now, in the bridge function, we write this:
[Objective-C]
-(GameObject*)createObject:(NSString*)type with:(NSDictionary*)args{
Class cls=NSClassFromString(type); // now cls is the class type like Tree/Stone/Apple
assert(cls);
return [[cls alloc] initWithArgs:args];
}
Summary & Further Discussion
The real question here is: can we use a string to create a class instance? Objective-C is quite dynamic and it offers such mechanic to create a class with a string. You may think further: can we make a function call from a string? The answer is YES. So far as you know the signature of the function, you can construct a function call dynamically. In Objective-C, a function is called a "selector" with its "implementation". Here's the code to construct a function call from a string:
// assume caller is the object, funcName is the function's name, and arg is the argument
SEL selector=NSSelectorFromString(funcName);
if ([caller respondsToSelector:selector]) {
IMP imp=[caller methodForSelector:selector];
id (*func) (id,SEL,NSString*)=(void*)imp;
assert(func);
return func(caller,selector,arg);
}else{
NSLog(@"Object does not respond to the function!");
return nil;
}
This is dynamic function call. If you want to get many properties of a game object via Lua, the worst thing to do is writing as many "bridge" functions in Objective-C. You should use dynamic function call like this:
[Lua]
game.getProperty(obj1, "x"); // get the x position of the game object "obj1"
game.getProperty(obj2, "collisionMask"); // get the collision mask bound to game object "obj2"
[Objective-C]
-(NSValue*)getProperty:(GameObject*)obj propertyName:(NSString*)name{
// here is a question for the reader of this post:
// can you implement this function by yourself?
}
Comments