In a separate thread I've mentioned that I've been working on an iOS visual novel engine. The core was actually cross platform done in C++, so now I'm attaching Cocos2DX onto it to go cross platform.
- the sample is at http://codeviewer.org/view/code:411e
I'm attaching a part of the script / the xml which is parsed to create the game. Currently the Invisible Apartment runs on this engine http://invisibleapartment.com
The format is quite flexible in that the pages and scenes are self contained which means that each scene has all the data (parameters) in itself to present it and each page too together with all the effects. I also believe that it's quite simple to understand.
I didn't like the idea of breaking the script down to labels and "e's" and defining all the objects at the beginning as is the way things are done in Ren'py so I made each object in the XML contain all the needed info.
Another advantage is that it's XML and it can be parsed easily by anyone.
On screen choices and jumping inside of the script is done in this way:
- you can jump to any position in the script as long as you label it (you can jump to scopes or scenes) whereas a scope is a collection of scenes, a scene is a collection of pages (each page can have overlays, text, effects, etc.)
Code: Select all
<choices title="What should the girl do?" default="NO">
<choice message="Slowly exit the cafe through the main entrance." action="sco01.00"/>
<choice message="Stay where you are." action="sco01.01"/>
<choice message="Flee through the kitchen and the back door." action="sco01.02"/>
</choices>
<scopes>
<scope id="sco01.00"> <!-- after this continue with main scope - the sce04 is partially here -->
<scenes>
<scene id="sco01.00.sce01">
<musiclist>
<music filename="">
</music>
</musiclist>
<widgets>
<image filename="IA_PreWork_Cafe_ver2.jpg"/>
<layer filename="IA_PreWork_Cafe_ver2.jpg" id="l01"/>
<textarea posx="5%" posy="80%" width="90%" height="20%">
<pages>
<page text="She takes a deep breath, slowly stands up from her table and heads to the exit.">
<overlays>
<overlay filename="IA_CharaPortrait_Kacey_Formal_prev2_glasses.png" posx="0.52" posy="0.2" w="0.45" h="0.80"/>
</overlays>
<actions>
<action type="layerMove" layerid="l01" mov="-20:+20"/>
</actions>
<musiceffects>
<musiceffect>
<music filename="Chair On Wood-SoundBible_com-1968083548.mp3"/>
</musiceffect>
</musiceffects>
</page>
</pages>
</textarea>
</widgets>
<onfinish goto="sco01:sce04"/>
</scene>
</scenes>
</scope>
<scope id="sco01.01"> <!-- she stays sitting at her table - bad choice, she overestimates herself blending in -->
<scenes>
<scene id="sce01.01.01">
You can define what to do at the end of each scene using
Code: Select all
<onfinish goto="sco01:sce04"/>
-
Here's an extract from the Invisible Apartment script:
-
Code: Select all
<script>
<scope id="sco01">
<scenes>
<scene id="sce01">
<musiclist>
<music filename="_TK_gymnopedie2-2.mp3"/>
</musiclist>
<widgets>
<layers>
<layer filename="IA_PreWork_Cafe_ver2.jpg" id="l01">
<effects>
<!-- <effect type="endlessScroll"/> -->
<effect type="boundaryScroll"/>
<!-- <effect type="brightnessFluctuationNO"/> -->
</effects>
</layer>
</layers>
<textarea posx="5%" posy="80%" width="90%" height="20%">
<pages>
<page text="We find ourselves in one of the business districts of New Jessica.">
<actions>
</actions>
<!--
<videoeffects>
<videoeffect>
<video filename="IA5v3mute.mp4"/>
</videoeffect>
</videoeffects>
-->
</page>
<page text="Business as usual. Customers are coming in during their lunch break.">
<actions>
<action type="layerMove" layerid="l01" mov="-20:+20"/>
<action type="alpha" layerid="l01" alpha="1.0"/>
</actions>
<musiceffects>
<musiceffect>
<music filename="Chair On Wood-SoundBible_com-1968083548.mp3"/>
</musiceffect>
</musiceffects>
</page>
<page text="A well dressed girl wearing sunglasses sits in the center of the cafe.">
<overlays>
<overlay filename="IA_CharaPortrait_Kacey_Formal_prev3_glasses.png" posx="0.5" posy="0.2" w="0.45" h="0.80"/>
</overlays>
</page>
<!-- who come here during their break -->
<page text="She blends in perfectly with the crowd of office workers. With a both confident and sneaky smile on her face she touches her coffee cup with her lips, barely reducing it by a drop.">
<overlays>
<overlay filename="IA_CharaPortrait_Kacey_Formal_prev3_glasses.png" posx="0.5" posy="0.2" w="0.45" h="0.80"/>
</overlays>
</page>
<page character="Girl" text="Are we in yet?!
She whispers to herself barely moving her lips.">
<overlays>
<overlay filename="IA_CharaPortrait_Kacey_Formal_prev2_glasses.png" posx="0.52" posy="0.2" w="0.45" h="0.80"/>
</overlays>
<actions>
<action type="layerMove" layerid="l01" mov="+40:-40"/>
</actions>
</page>
</pages>
</textarea>
</widgets>
</scene>
Multiple layers compose the background:
- this example lets the layer behind the window move (the window layer has an alfa channel)
Code: Select all
<scene id="sce21">
<musiclist>
<music filename="Bad Space.mp3">
</music>
</musiclist>
<widgets>
<image filename="IA_BG_Apartment_All.jpg"/>
<layers>
<layer filename="Skyline.jpg" id="l01">
<effects>
<effect type="boundaryScroll"/>
</effects>
</layer>
<layer filename="IA_BG_Apartment_Trans.png" id="l02">
</layer>
</layers>
To make a port of the whole thing you should simply be able to know how to position layers on a screen which hold images that have an alfa channel (png images), you should be able to dynamically position text on the screen, play audio and video.
-
You see that the overlays (sprites) are positioned relatively (0.1, 0.9, etc.)
<overlay filename="IA_CharaPortrait_Kacey_Formal_prev3_glasses.png" posx="0.5" posy="0.2" w="0.45" h="0.80"/>
- that would position the sprite at 0.5*width & 0.2*height and the width would be 0.45*width & height would be 0.8*height.
To make sprites in Cocos2DX position according to these attributes I did this:
Code: Select all
struct CMPFRAME {
CCPoint origin;
CCSize scale;
};
/**
0.1 = parent*0.1
*/
struct CMPRELATIVEFRAME {
CCPoint origin;
CCSize size;
};
bool setFrameForDynamicSprite( CCSprite *sprite, CMPRELATIVEFRAME relframe )
{
printf("setFrameForDynamicSprite\n");
if (!sprite)
return false;
CCSize s = CCDirector::sharedDirector()->getWinSize();
relframe.origin.y = 1.0f-(relframe.size.height+relframe.origin.y);
// set scale
float scale = 1.0f;
float boxwidth = s.width*relframe.size.width;
float spritewidth = sprite->getContentSize().width;
float boxheight = s.height*relframe.size.height;
float spriteheight = sprite->getContentSize().height;
// we try to figure out which sides relations to take in account when scaling - we always scale to fit, not fill
if (
sprite->getContentSize().height > sprite->getContentSize().width
)
{
printf(" boxheight(%f) spriteheight(%f)\n",boxheight,spriteheight);
if (relframe.size.height/relframe.size.width < sprite->getContentSize().height/sprite->getContentSize().width)
scale = boxheight / spriteheight;
else
scale = boxwidth / spritewidth;
}
else
{
printf(" boxwidth(%f) spritewidth(%f)\n",boxwidth,spritewidth);
if (relframe.size.height/relframe.size.width > sprite->getContentSize().height/sprite->getContentSize().width)
scale = boxwidth / spritewidth;
else
scale = boxheight / spriteheight;
}
sprite->setScaleX(scale);
sprite->setScaleY(scale);
// set position
CCPoint cp = CCPointMake(
(s.width*relframe.origin.x)+((s.width*relframe.size.width)/2.0f),
(s.height*relframe.origin.y)+((s.height*relframe.size.height)/2.0f)
);
sprite->setPosition(cp);
printf(" position(%fx%f) spriteSize(%fx%f)\n",
cp.x,cp.y,
sprite->getContentSize().width*scale,sprite->getContentSize().height*scale);
return true;
}