In this post I do not want to deal with theories of dialogue. I will try to summarize the requirements for the implementation of dialogue in a computer game. In the next post I am going to implement such a tool.
Type of dialogs:
Linear
Linear interrupted
Linear dialogue with questions
Branched dialogue with different results
Fake branched dialogue with the same end
Procedural dialogue
Combined dialogue
Linear dialogue
Example:
Start: player: Do you know anything about the cave outside the village?
1 character: People is losing there.
2 player: How to get there?
3 character: Follow the river.
4 player: How much time does it take path.
End: About 14 hour.
Linear interrupted
A player may ask follow-up questions.
Example:
1 character: People is losing there.
1a player: Tell me more please.
1a character: ...
3 character: Follow the river.
3a player: I need a guide.
3a character: ...
Linear dialogue with questions
Extension topics are in a separate thread. May contain more information. Information thread can still be active, even if you repeat the dialogue. Information thread may include comprehensive encyclopedic knowledge, such as the history (Vietcong 2).
Branched dialogue with different results
Large amounts of data, even in a short dialogue.
Fake branched dialogue with the same end
The player has the feeling that the conversation affects.
Procedural dialogue
It contains variables that affect the result of conversation.
Key features
Visualization (GUI tool )
Conditions
Variables
Outgoing Links
Export to JSON
1. Visualization I am going to do a GUI tool (dynamic HTML form) for designing dialogue. GUI tool also will be able to show dialogue graph.
2. Conditions Ability to show and hide a branch based on conditions. (See Procedural dialogue).
Example:
isFirstVisit == true
hasSword == true
mood >= 10
inventory.contains(“amulet”)
3. Variables Can set variables, increment, decrement. (See Procedural dialogue).
Example:
hasSword = true
mood += 2
4. Outgoing Links Each item can have a reference (0..*) to another item in the dialog.
5. Export to JSON I am going to export data structure in JSON. Despite its relationship to JavaScript, it is language-independent, with parsers available for many languages.
Example of dialogue
Simple dialogue, but contains: links, conditions, variables.
Proposed JSON
It may interest you to know
Chat Mapper Chat Mapper is tool for writing and testing nonlinear dialogue, especially for video games and training. Free for noncommercial use (no export).
The JMeter is desktop/ command line Java application designed to load test functional behavior and measure performance. It was originally designed for testing Web Applications.
This article shows how to use it for testing webapps.
Preparations
Install JMeter on your laptop. On Linux is as package in some central repositories.
Install htop on your server. htop is top on steroids. You can find it on rpmforge repos.
Getting Started
It shows how to define basic request for server and dump reports.
1. Adding Thread Group to Test Plan
Number of Threads: Defines how many threads (users) will access to the server.
RampUp Period: In what time period the requests shall be made. Zero means immediately.
2. Adding Reguest to Thread Group Type in the Server name, Port number, Path.
3. Adding Listeners to Thread Group Listeners used to capture and presentation of results. For example: Spline Visualizer, Summary Report, View Requests in Table.
4. Starting basic test. Go to server and run htop for watching server status dynamically. You can see: CPU and memory usage, Swap usage, processes, …
From JMeter main menu Run>Start. During testing watch the behavior of the server on htop console. It may help to understand more your server. After completing the test explore results in Listeners sections: errors, times, responses,...
Using proxy server for recording complex requests
For a definition of complex requests is best to use the HTTP Proxy Server.
Insert HTTP Proxy Server component to WorkBench fork. Define port and click to Start button.
Insert Recording Controller to Thread Group.
Connecting to HTTP Proxy Server Go into the settings of your favorite web browser and connect to the proxy server.
Now, in your web browser go to your webapp and make requests. For example fill out and submit the form. All your requests are stored in the Record Conroller.
At the end don´t forget stop the HTTP Proxy server. Start test and watch results.
Conclusion
JMeter offers a lot of others components: Timers, Logic Controllers, Assertions for checking responses. You can test SOAP and XML/RPC services, JDBC connections, FTP requests.
JMeter can be run from the command line. You can test automatically from scripts, or on server without GUI.
Google Apps Script is a scripting language that provides easy ways to automate tasks across Google Drive. This tutorial explains a script that sends an email confirmation after submitting the Google Form. Sending email from GAS is very simple:
function sendEmail() {
MailApp.sendEmail(“customer@gmail.com”, “Confirmation”, “Hello customer.”);
}
Usually there is a need to customize the email body. For example from Google Spreadsheet:
function sendEmail() {
var message = “Hi ${firstname} ${lastname}, Thank you for buying gun.”
MailApp.sendEmail( email , “Confirmation”, message);
}
After submitting Google Form script finds the last row in the spreadsheet and puts value instead of the column name. For example from ${firstname} to John. When you want to send a comprehensive email, it might be better to use HTML email body.
function sendEmail() {
var message = createTemplateFromFile( TEMPLATE_FOR_CUTOMER );
MailApp.sendEmail( email, “Confirmation”, "", {htmlBody: message});
}
HTML template for customer
And this is the idea of the script. After submitting Google Form script finds last row from spreadsheet and puts value to html template. HTML template, you can change and expand as your needs. Filled template is used as email body. Trigger The last important task is to set the trigger. From the main menu of Script Editor click to: Resources > All your triggers
You can download Gaelyk Template project from Gaelyk website or visit Gaelyk on Github. The current version - v1.2 - of Template project on Gaelyk website contains a build problem, which is already fixed, but it is not included in zip package of the Template project on Gaelyk websites.
Fix gradle.build
After downloading the package, it is necessary to change the file gradle.build.
Yesterday I got IPcam DCS-950G. I wanted to connect camera to Motion but it did not get stream. I had to use stupid IE only. I was angry and looking for solution. I found people just as disappointed as I am. I decided to solve this problem.
I went to D-Link and study specification. After that I downgrade camera to DCS-950G_ A1_Firmware_v1.00 because the camera with this old firmware does not need admin authentication for streaming. It's so convenient. I do not have to send with each request the password.
I wanted to write a Groovy script, but I wanted to control the camera from the router, which I do not have Java. That's why I chose Bash.
After hours of testing, I had written a working script:
I this article I develop a JavaFX game in Netbeans. You can see result of me game:
I wont deploy game for mobile phone. I try on the game in mobile emulator in Netbeans on Windows. It run fine. In Linux we have not mobile emulator yet (28.11.2009).
The source code of this game as Netbeans project are here.
Thanks for your comment under article.
Graphics
For create graphics I use Inkscape (0.47).You can look find more about JavaFX and Inkscape for example in Silveira Neto. But Inkscape 0.47 was release yet and You can not compile source code of Inkscape.
At the end I export images wolfs and sheep from svg format to png. As splash image I let svg format export in Inkscape to JavaFX class, but I was supprised that not more difficult svg image have a lots rows in JavaFX class. About 20 tausend. That big class is not work well in Netbeans editor.
Source graphics.svg file.
Switch between splash and game screen use in Brick JavaFX game.
Let me introduce the code.
Config.fx
All game setting is in Config.fx file. You can see setting as scene width and height, some images path, animation time, game speed and etc.
public def SCENE_WIDTH = 240;
public def SCENE_HEIGHT = 320;
public def SCENE_INFOPANEL_HEIGHT = 25;
public def MARGIN = 5;
public def WOLF_IMAGE_WIDTH = 100;
public def WOLF_IMAGE_HEIGHT = 50;
public def SHEEP_IMAGE_WIDTH = 50;
public def SHEEP_IMAGE_HEIGHT = 50;
public def ANIMATION_TIME = 0.04s;
public def COUNT_OF_LIVE = 3;
public def SHEEP_IMAGES = [ Image {url: "{__DIR__}sheep_waiting.png"},
Image {url: "{__DIR__}sheep_lu.png" }, // left up
Image {url: "{__DIR__}sheep_ld.png" }, // left down
Image {url: "{__DIR__}sheep_ru.png" }, // right up
Image {url: "{__DIR__}sheep_rd.png" } // right down
];
public def WOLF_IMAGES = [
Image {url: "{__DIR__}wolf_jump1_left.png" },
Image {url: "{__DIR__}wolf_jump2_left.png" },
Image {url: "{__DIR__}wolf_jump3_left.png" },
Image {url: "{__DIR__}wolf_jump1_right.png" },
Image {url: "{__DIR__}wolf_jump2_right.png" },
Image {url: "{__DIR__}wolf_jump3_right.png" }
];
//--------------------------------WOLFS
public def WAITING_DIRECTION = 0;
public def LEFT_UP_DIRECTION = 7;
public def LEFT_DOWN_DIRECTION = 1;
public def RIGHT_UP_DIRECTION = 9;
public def RIGHT_DOWN_DIRECTION = 3;
public def WOLFS_AGGRESSIVENESS = 100;
public def WOLF_SPEED = 1000;
Wolf.fx
Class Wolf has two timelines. First for jump wolf to sheep in timeline attack.
In this timeline I define path where will bee wolf jump. You can set the direction of path when the instance of class Wolf create. The second timeline move I set image cyklus for sprite.
Wolf has function start and stop. This function starting and stopping mentioned timelines.
Wolf has important function wantAttack():Boolean in which it decide when wolf start attack. This function use Random.
public class Wolf extends CustomNode{
public var image:Image;
public-init var attackPath:Path;
public var direction:Integer;
var aggressiveness = Config.WOLFS_AGGRESSIVENESS;
var random : Random = new Random();
var frame = 0;
public function start() {
move.play();
attack.playFromStart();
}
public function stop() {
move.stop();
attack.stop();
}
protected override function create(): Node {
return Group {
content: [
ImageView{
image: bind image;
}
]
} // Group
}
public function wantAttack():Boolean{
if ( (not attack.running) and random.nextInt( aggressiveness ) == 0){
return true; //==========>
}
return false;
}
public def attack = PathTransition {
node: this
path: bind AnimationPath.createFromPath(attackPath)
orientation: OrientationType.NONE;
interpolator: Interpolator.EASEIN
duration: Duration.valueOf( random.nextInt( Config.WOLF_SPEED) + 1000 );
repeatCount: 1
autoReverse: true
action: function() {
Main.mainFrame.lifeCount--;
}
};
def move = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time : 1s/8
action: function() {
if(direction.equals(Config.LEFT_UP_DIRECTION) or direction.equals(Config.LEFT_DOWN_DIRECTION)){
image = Config.WOLF_IMAGES[ (++frame mod 3) ] ;
}else{
image = Config.WOLF_IMAGES[(++frame mod 3) + 3] ;
}
}
}
}
}
Sheep.fx
Class Sheep have only functions for image change. The variable direction keep info about direction where is sheep turn.
public class Sheep extends CustomNode{
public-read var image:Image;
public-read var direction:Integer;
public function waiting():Void{
direction = Config.WAITING_DIRECTION;
image = Config.SHEEP_IMAGES[0];
}
public function leftUp():Void{
direction = Config.LEFT_UP_DIRECTION;
image = Config.SHEEP_IMAGES[1];
}
public function leftDown():Void{
direction = Config.LEFT_DOWN_DIRECTION;
image = Config.SHEEP_IMAGES[2];
}
public function rightUp():Void{
direction = Config.RIGHT_UP_DIRECTION;
image = Config.SHEEP_IMAGES[3];
}
public function rightDown():Void{
direction = Config.RIGHT_DOWN_DIRECTION;
image = Config.SHEEP_IMAGES[4];
}
protected override function create(): Node {
Group {
content: [
ImageView {
image:bind image;
}
]
} // Group
}
}
Splash.fx
Class content splash image. It contents function start() and stop() where You can add some timelines as in Brick game. I use svg image exported from Inkscape as I mentioned above.
public class Splash extends CustomNode{
def intro = Intro{};
def background = Group{
focusTraversable: true;
content:[
Rectangle {
x:0;
y:0;
width:Config.SCENE_WIDTH;
height: Config.SCENE_HEIGHT;
fill: Color.web("#00bfff");
},
intro
]
onKeyPressed: function( e: KeyEvent ):Void {
Main.mainFrame.startGame(Config.COUNT_OF_LIVE);
}
}
public function start(){
background.requestFocus();
}
public function stop(){
}
override public function create(): Node {
Group {
content: [
background
]
};
}
}
Level.fx
This is class for game logic. There is class Sheep and Wolfs construct. Every Wolf has different attackPath and direction. There are function start() and stop() for play main timelines: gameLogics. In this timeline is function checkWolfsAttack(wolfs) who check Wolfs will attack. There is also collision detection as I mentioned above.
public class Level extends CustomNode{
var state = 0;
def textGameOver = Text{
translateX:Config.SCENE_WIDTH /4;
translateY:Config.SCENE_HEIGHT/4;
font: Font.font("SansSerif",FontWeight.BOLD,20)
content: "GAME OVER.";
visible:false;
}
def infoPanel = InfoPanel{};
var hill = Arc {
centerX: Config.SCENE_WIDTH/2;
centerY: Config.SCENE_HEIGHT;
radiusX: Config.SCENE_WIDTH/3;
radiusY: Config.SCENE_HEIGHT/3;
startAngle: 0;
length: 180;
type: ArcType.OPEN
fill: Color.GREEN
}
var sheep = Sheep{
translateX:Config.SCENE_WIDTH /2 - Config.SHEEP_IMAGE_WIDTH /2;
translateY:(Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3) - Config.SHEEP_IMAGE_HEIGHT;
};
var wolf1 = Wolf{
direction:Config.LEFT_DOWN_DIRECTION;
translateX:- Config.WOLF_IMAGE_WIDTH; //outside
attackPath:Path{
elements: [
MoveTo { x:-Config.WOLF_IMAGE_WIDTH y: (Config.SCENE_HEIGHT - Config.SCENE_INFOPANEL_HEIGHT) - Config.WOLF_IMAGE_HEIGHT/2 },
QuadCurveTo { x: Config.SCENE_WIDTH /2;
y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
controlX: Config.SCENE_WIDTH /6;
controlY: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
}
]
}
};
var wolf2 = Wolf{
direction:Config.LEFT_UP_DIRECTION;
translateX:- Config.WOLF_IMAGE_WIDTH; //outside
attackPath:Path{
elements: [
MoveTo { x:-Config.WOLF_IMAGE_WIDTH y: Config.WOLF_IMAGE_HEIGHT/2 },
QuadCurveTo { x: Config.SCENE_WIDTH /2;
y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
controlX: Config.SCENE_WIDTH/2;
controlY: Config.WOLF_IMAGE_HEIGHT;
}
]
}
};
var wolf3 = Wolf{
direction:Config.RIGHT_UP_DIRECTION;
translateX:- Config.WOLF_IMAGE_WIDTH; //outside
attackPath:Path{
elements: [
MoveTo { x: Config.SCENE_WIDTH + Config.WOLF_IMAGE_WIDTH y: Config.WOLF_IMAGE_HEIGHT/2 },
QuadCurveTo { x: Config.SCENE_WIDTH /2;
y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.SHEEP_IMAGE_HEIGHT/2;
controlX: Config.SCENE_WIDTH/2;
controlY: Config.WOLF_IMAGE_HEIGHT;
}
]
}
};
var wolf4 = Wolf{
direction:Config.RIGHT_DOWN_DIRECTION;
translateX:- Config.WOLF_IMAGE_WIDTH; //outside
attackPath:Path{
elements: [
MoveTo { x: Config.SCENE_WIDTH + Config.WOLF_IMAGE_WIDTH y: (Config.SCENE_HEIGHT - Config.SCENE_INFOPANEL_HEIGHT) - Config.WOLF_IMAGE_HEIGHT/2 },
QuadCurveTo { x: Config.SCENE_WIDTH /2;
y: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3)- Config.WOLF_IMAGE_HEIGHT/2;
controlX: Config.SCENE_WIDTH - Config.SCENE_WIDTH /6;
controlY: (Config.SCENE_HEIGHT - Config.SCENE_HEIGHT/3) - Config.WOLF_IMAGE_HEIGHT/2;
}
]
}
};
var wolfs: Wolf[] = [wolf1,wolf2,wolf3,wolf4];
var group: Group;
public function start() {
gameLogics.play();
group.content[0].requestFocus();
state = 2;
}
public function stop() {
gameLogics.stop();
}
def gameLogics = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time : Config.ANIMATION_TIME;
action: function() {
if (state != 2) {
return; //==========>
}
if (Main.mainFrame.lifeCount == 0){
gameOver();
}
checkWolfsAttack(wolfs);
}
}
};
override public function create(): Node {
group = Group {
content: [
Rectangle{
focusTraversable: true
x:0;
y:0;
width:Config.SCENE_WIDTH;
height:Config.SCENE_HEIGHT;
fill: Color.web("#00bfff");
onKeyPressed: function( e: KeyEvent ):Void {
//skip to begin
if (state == 3 and e.code == KeyCode.VK_5){
Main.mainFrame.state = 0;
}
if (e.code == KeyCode.VK_7) {
sheep.leftUp();
} else if (e.code == KeyCode.VK_1) {
sheep.leftDown();
}else if (e.code == KeyCode.VK_9) {
sheep.rightUp();
}else if (e.code == KeyCode.VK_3) {
sheep.rightDown();
}
checkCollision(wolfs);
}
onKeyReleased: function( e: KeyEvent ):Void {
sheep.waiting();
}
},
hill, sheep, wolf1, wolf2, wolf3, wolf4, infoPanel, textGameOver
]
}
}
function checkCollision(wolfs:Wolf[]):Void{
for(wolf in wolfs){
if(wolf.attack.running and sheep.direction.equals(wolf.direction) and isCollision(wolf)){
hit(wolf);
}
}
}
function isCollision(wolf:Wolf): Boolean {
return (collision(wolf.translateX, wolf.translateY, wolf.translateX + wolf.image.width, wolf.translateY + wolf.image.height, sheep.translateX, sheep.translateY, sheep.translateX + sheep.image.width, sheep.translateY + sheep.image.height));
}
function collision(ax, ay, bx, by, cx, cy, dx, dy): Boolean {
return not ((ax > dx)or(bx < cx)or(ay > dy)or(by < cy));
}
function hit(wolf:Wolf):Void{
wolf.stop();
wolf.translateX = - Config.WOLF_IMAGE_WIDTH;
Main.mainFrame.score++;
}
function checkWolfsAttack(wolfs:Wolf[]){
for(wolf in wolfs){
if (wolf.wantAttack()){
wolf.start();
}
}
}
function gameOver(){
textGameOver.visible = true;
sheep.visible = false;
for (wolf: Wolf in wolfs){
wolf.visible = false;
}
state = 3;
}
}
Main.fx
You can see switch between splash and level
public var mainFrame: MainFrame;
function run(__ARGS__ : String[]) {
mainFrame = MainFrame {
title: "Wolfs attaks"
resizable: false
scene: Scene {
width:Config.SCENE_WIDTH;
height:Config.SCENE_HEIGHT;
}
}
}
public class MainFrame extends Stage {
// Instance of splash (if exists)
var splash: Splash;
// Instance of level (if exists)
var level: Level;
// Number of lifes
public var lifeCount: Integer;
// Current score
public var score: Integer;
// Initializes game (lifes, scores etc)
public function startGame(lifeCount:Integer) {
this.lifeCount = lifeCount;
score = 0;
state = 1;
}
// Current state of the game. The next values are available
// 0 - Splash
// 1 - Level
public var state: Integer = 0 on replace {
if (state < 1 ) {
splash.stop();
level.stop();
level = null;
splash = Splash {};
scene.content = [
splash
];
splash.start();
} else {
level.stop();
splash = null;
level = Level {}
scene.content = [
level
];
level.start();
}
};
}
Conclusion
Game run fain in mobile emulator in Netbeans. On real device we must wait yet. I look forward my new mobile phone will be support JavaFX profile.
package cz.kibo.client;
import java.io.Serializable;
public class Person implements Serializable{
public String name;
public Person(){
this(null);
}
public Person(String name) {
super();
this.name = name;
}
}