Lua Scripting

I started off early with Lua scripting. This was mainly because the Ray Wenderlich RPG samples were using it and this gave me a head start and a direction. I think Lua is a beautiful scripting language, and it has real potential (see the Love2D framework).

It’s what I can teach my 9 year old son. So it’s pretty nice.

I am still in pre-production and I wanted to make coding the actor (NPC and player) actions as easy as possible. If you settle to early for something simple to progress quickly, it can be really cumbersome when you need to make a lot of changes to rooms and actors.

I made a pickup animation sequence completely in Objective C, but I knew this was wrong.
If I had to change or extends this I had to change about 40 lines of code and it wasn’t flexibal.
This was wrong because the behavior of actors is data but in the form of scripts. I have one script per room.
As you can see this is much nicer and flexible :

local seqOpen = cs.SequenceFactory.new("dave");
seqOpen.walkTo("{6,5}");
seqOpen.animationBackAndForth("pickupsideways");
seqOpen.addInventory("hammer");
seqOpen.wait("0.8");
seqOpen.script("passHammerToDave");
seqOpen.start();

This script will :

  • Walk the main character to the item.
  • He will kneel to pick it up and will rise again. This is a back and forth animation
  • Add the item to the inventory of the player
  • Wait for about a second and run a lua method called passHammerToDave that alters the state of the character. Dave will ‘own’ the item now.

Now it passes all these actions to the CCActionSequence that is supported by the Cocos2D framework. The seqOpen will be passed as an array of commands to the Objective C bridging code, and this will execute this in sequence as CCAction’s! Many actions in cocos2d are finite time actions, that means that walking to a certain tile with the help of an A* (Astar) algorithm does not fit easily into an action. So I splitted all separate CCAction sequences and let every sequence end with an action to fire an event to a sort of action manager class that starts the next sequence.

This looks like this :

    NSMutableArray *preppedActions = [NSMutableArray array];

    for (NSString * scriptingLine in luaStringTableArray) {
        if ([scriptingLine hasPrefix:@"walkto"]) {
            id blockToExecute = [CCActionCallBlock actionWithBlock:^{
                CCLOG(@"scripted walkto reported");

                NSString * coords = [LuaUtility firstArgFromScriptLine:scriptingLine];
                player.moving = YES;
                [player moveTowardsTileCoordWithAi:CGPointFromString(coords)];
            }];
            [preppedActions addObject:blockToExecute];
        }else if([scriptingLine hasPrefix:@"wait"]){
            NSString *delay = [LuaUtility firstArgFromScriptLine:scriptingLine];
            CCTime timedDelay = [delay floatValue];
            id actionDelay = [CCActionDelay actionWithDuration:timedDelay];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"waiting finished"];
            }];

            id sequenceToExecute = [CCActionSequence actionOne:actionDelay two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        }
        else if([scriptingLine hasPrefix:@"animate"]){
            NSString *animationName = [LuaUtility firstArgFromScriptLine:scriptingLine];

            id blockToStartAnimation;
            if([scriptingLine hasPrefix:@"animateonce"]){
                blockToStartAnimation = [CCActionCallBlock actionWithBlock:^{
                    [player startOneTimeAnimation:animationName];
                }];
            }else if ([scriptingLine hasPrefix:@"animatebackandforth"]){
                blockToStartAnimation = [CCActionCallBlock actionWithBlock:^{
                    [player startBackAndForthAnimation:animationName];
                }];
            }

            if(blockToStartAnimation){
                CGFloat animationDuration = [player animationDuration:animationName];
                id delayFromPickingUpAnimation = [CCActionDelay actionWithDuration:animationDuration];
                id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                    [player.characterAnimationManager eventReportedOn:player event:@"animation finished"];
                }];

                id sequenceToExecute = [CCActionSequence actions:blockToStartAnimation,delayFromPickingUpAnimation, blockToFireEvent,nil];
                [preppedActions addObject:sequenceToExecute];
            } else{
                CCLOG(@"WARN : syntax of the animate command was not correct, it needs to be animateonce or animatebackandforth it was %@",scriptingLine);
            }

        }else if([scriptingLine hasPrefix:@"script"]){
            id blockToExecute = [CCActionCallBlock actionWithBlock:^{
                [luaScriptProcessor runVoidScript:[LuaUtility firstArgFromScriptLine:scriptingLine]];
            }];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];
            id sequenceToExecute = [CCActionSequence actionOne:blockToExecute two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        } else if([scriptingLine hasPrefix:@"move"]){
            id moveAnInch = [CCActionMoveBy actionWithDuration:0.2f position:CGPointMake(20,0)];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];
            id sequenceToExecute = [CCActionSequence actionOne:moveAnInch two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        }  else if([scriptingLine hasPrefix:@"playsound"]){
            id blockToPlaySound = [CCActionCallBlock actionWithBlock:^{
                [[AudioManager sharedManager] playSoundEffect:[LuaUtility firstArgFromScriptLine:scriptingLine]];
            }];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];

            id sequenceToExecute = [CCActionSequence actionOne:blockToPlaySound two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        }
        else if([scriptingLine hasPrefix:@"addinventory"]){
            id blockToAddInventory = [CCActionCallBlock actionWithBlock:^{
                NSString *sceneItemName = [LuaUtility firstArgFromScriptLine:scriptingLine];
                [[InventoryManager sharedManager] addInventoryItemByName:sceneItemName];
                [tilemapTool removeObjectFromMap:sceneItemName];
            }];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];

            id sequenceToExecute = [CCActionSequence actionOne:blockToAddInventory two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        }


        else if([scriptingLine hasPrefix:@"gotoscene"]){
            id blockToGotoScene = [CCActionCallBlock actionWithBlock:^{
                //TODO Pass specific roomid in case of roomchange
                NSString *roomId = [LuaUtility firstArgFromScriptLine:scriptingLine];
                SwitchRoomScreen *scene = [[SwitchRoomScreen alloc] init];
                [[CCDirector sharedDirector] pushScene:scene];
            }];
            [preppedActions addObject:blockToGotoScene];
        } else if([scriptingLine hasPrefix:@"tileon"]){
            id blockToPutTileOn = [CCActionCallBlock actionWithBlock:^{
                NSString *tileCoords = [LuaUtility firstArgFromScriptLine:scriptingLine];
                CCTiledMapLayer *wallLayer = [tilemapTool.tileMap layerNamed:@"collisiontiles"];
                [wallLayer setTileGID:1 at:CGPointFromString(tileCoords)];
            }];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];
            id sequenceToExecute = [CCActionSequence actionOne:blockToPutTileOn two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        } else if([scriptingLine hasPrefix:@"tileoff"]){
            id blockToPutTileOn = [CCActionCallBlock actionWithBlock:^{
                NSString *tileCoords = [LuaUtility firstArgFromScriptLine:scriptingLine];
                CCTiledMapLayer *wallLayer = [tilemapTool.tileMap layerNamed:@"collisiontiles"];
                [wallLayer setTileGID:0 at:CGPointFromString(tileCoords)];
            }];
            id blockToFireEvent = [CCActionCallBlock actionWithBlock:^{
                [player.characterAnimationManager eventReportedOn:player event:@"script finished"];
            }];
            id sequenceToExecute = [CCActionSequence actionOne:blockToPutTileOn two:blockToFireEvent];
            [preppedActions addObject:sequenceToExecute];
        }
    }

    [player.characterAnimationManager loadActionsFromArray:preppedActions];
    [player.characterAnimationManager startAnimations:player];
    player.characterAnimationManager.continuous = continuous;

A lot of code, for sure. But this is what I need to do once… Maybe the code needs some cleaning up, but for now it’s ok. It does the job well. Every action will be perfectly in sequence because each action will fire an event to the characterAnimationManager to signal that the next sequence should be executed.

The next scripts will show the scripting for opening the elevator door and going to another room :

local seqOpen = cs.SequenceFactory.new("dave");
sequence.walkTo(room.objectElevatorDoor.buttonlocation);
sequence.animationOnce("reachcentre");
sequence.playSound("button.caf");
sequence.wait("0.2");
sequence.playSound("slidedoors.caf");
sequence.switchCollision(room.objectElevatorDoor.standingLocation, "off");
sequence.script("openElevatorDoor");

The script does the following steps:

  • The player walks to the button location that is defined in the room he is in.
  • The player will reach for the button when it uses the verb “use” on the control panel
  • Button sound plays (this is alway an asynchronous event)
  • The sound for sliding the elevator is played
  • The elevator has some tile locations that needs to be blocked or unblocked when doors close or open up.
  • The sequence runs the open elevator door animations that is called via lua via function ‘openElevatorDoor’

Well lets see these both scripts in action! (trust me… it’s quite short) :

Scripting from franzzle on Vimeo.

Archived: Uncategorised