Scrolling Game Development Kit Forum
SGDK Version 1 => Script => Topic started by: eric22222 on 2006-11-29, 09:16:59 PM
-
We finally got out of the physics testing stage and got into the real making of the game! We're anticipating a lot of dialogue, like, the same NPCs say different things at different points in the game. I figure it'd be much easier to create the special functions dynamically. I'd like to have one special function per NPC to raise an event. I'll test some value "progress" in script and create and immediately trigger a message box function.
I saw in an earlier thread (and in the GoldYoink code) how to create a function for changing maps, but I'll hazard a guess that message boxes are a little different. So, um... how exactly do I do that?
-
So you know how to create a SpecialFunction object by looking at GoldYoink code and just need to know how to make it a message function? I think I can help there. I'll just look at the GameDev source code to see what it sets when creating a message function.
Heh -- it appears to be very simple: the only thing unique to a message function is the ".Value" property which contains the message. All the other properties are set by common code that set properties for all special functions (FuncType, Flags, Flags2, InvUseCount). Oh, and the FuncType value for a message function would be SPECIAL_MESSAGE (or simply the value 1 if you have trouble using the named constant).
Does that help?
-
I think that's all I need to know...
But just to be clear on syntax, I'd do something like
"Some message." = MessageFunction.Value
Right? Like, that's the correct way to use quotation marks?
-
Technically it would be the other way around:
MessageFunction.Value = "Some message."
But the quotations are correct, yes. And if you want to concatenate several strings together, you simply put plus signs between them. For example:
dim playerName
playerName = "Fred"
'...Other code
MessageFunction.Value = "Oh, " + playerName + ". I see you've come back."
Which would show a message saying Oh, Fred. I see you've come back. This is useful for using variable strings in messages, where the player, for example, can specify their name. You could also use this to display an amount of inventory, such as amount of gold you have, or something, within a string.
-
Now that sounds cool. I'm gonna have to make use of that somehow. Thanks!
-
I've started working on the script for the message boxes and had an idea to make things much easier. This was my first semester after changing to CS, so I've only recently learned about strings. Anyway, I have a small box around each NPC as a special function activated with shift. I have each one raise an event. I want to all the generic message stuff at once and just tailor the message. Example...
If func.name="NPC_001" or func.name="NPC_002" or... then
Set to global, remove after use, etc.
If func.name="NPC_001" Then Message Value= "Here's a message"
If func.name="NPC_002" Then Message Value= "Here's another"
Etc.
End If
However, this will require me to list EVERY NPC in the whole game that gets a message. Anyway, we recently discussed string comparison in class, so I got to thinking. Could I replace that first line with something like:
If func.name>"NPC_000" and func.name<"NPC_999"
Would that work in VB? If not, feel free to offer another way around this. Or if this way of doing things could be replaced with something more efficient, I'd be up for that, too.
-
That would work, yes. However, this does of course restrict you to 998 messages throughout the game. Here's a way I would do this:
if left(func.name,4) = "NPC_" then
dim strTmp
strTmp = mid(func.name,5)
select case strTmp
case 1
Message = "Here's a message"
case 2
Message = "Here's another"
end select
end if
This allows for functions to be named NPC_1, NPC_2, etc. or whatever you want, really. You could call them NPC_Message and just use
case "Message"
but that's getting a little bit more confusing, since you're using an untyped variable and checking for multiple datatypes. If you just stick to numbers, this works well. Also, using a select case statement is faster, I think, than using a multiple-alternative if statement. This may not be true in the case of VBS due to the lack of variable types. In any case, it's easier to make another message.
-
:o
*stares in amazement*
I could I use that "left" syntax on things like sprite identification, right? Like, in the tutorial, it demonstrates finding sprites of the definition "bomb". I've been using that for a while, but since I'm gonna have multiple maps, I could check just the left part of the name, right?
left(Def.Name.9)="Bug_enemy"
Would work for all sprites that start with that? I'm not really asking, so much as realizing how much work this will save when we start branching into multiple maps.
*Bakes cookies for durnurd*
-
Well, almost, anyway, yes. Use a comma to separate the string name from the number of characters to take. That is, use
if left(Def.Name,9)="Bug_enemy" then
instead of
if left(Def.Name.9)="Bug_enemy" then
-
How is this easier than just defining the messages in the IDE? Or what does it accomplish that entering the messages as regular functions in the IDE doesn't?
-
Well, we want to have a LOT of dialogue in this game. We want this to have an RPG feel in expansiveness and character development. One thing that is absolutely necessary in such a game is changing dialogue. I want to keep up with a variable "Progress" that keeps track of how far along in the game the player is. Technically I could do this without script, but it'd be pretty complex. I'd have a special function for each different message in the game instead of for each NPC. While I could do a "must have X of Progress" thing, this wouldn't remove the previous messages. Like, when you get to the second message, it'd display both the first and second since you meet the requirements of Progress for both.
It would also be a hassle if moving an NPC over a few tiles was ever necessary. Now I'd only have to remake one special function instead of three or more.
-
Now that I understand that, maybe I can offer a suggestion for a clever trick you could try. Use the text of each message function to store all the data about the messages that will appear there, and then make the script just parse it instead of making the script store all the script text. That way it might be easier to see, manage and update which text goes with which NPC. So for example, you could create a message like this:
#COND 1=0
Oh woe is me, I have lost my wedding ring! Who are you? %PLAYER%? Can you help me find my ring, %PLAYER%?
#COND 1=1
You found it! Thank you so much.
#COND 1=2
Thanks again for finding my ring, %PLAYER%!
Then in script code, during startup, you could change the type of every message function that contains "#COND" to be SPECIAL_EVENT so that the function no longer displays a message, but just triggers an event to allow the script to do its work. The script, when it received a special function activation event, would parse the string in the Value property of the activated function, which would still contain the original message string. Using the information after each #COND, it could determine which condition is met (in this example, I figured you might want to be checking the number in inventory item #1, and when it is 0, display the first segment, when it is 1, display the second, and when it is 2, display the last segment. That's what the "1=0", "1=1" and "1=2" are for). After the script determines which portion of the string represents the message it should display, it changes the text (Value property) of some off-screen special function object and calls ActivateFunction on it to display the message.
You could use code similar to what you can find in the engine (I can help you find it if you want) -- the code which parses the font name and color at the beginning of the message. It parses the string line by line using InStr to find the end of each line (vbLf) and Left to extract the text. If it sees "#FCL" at the beginning of any line, it removes it from the message text and changes the font color. In your case, you could check for "#COND" at the beginning of a line, and, in this case, see the "1=0" and parse that into some code that knows that this means you have to have 0 of inventory number 1 in order for this segment of the message to display.
This might sound complicated, but keep in mind that you'd write this code once (I'm guessing it'd be maybe 30-40 lines -- not too bad compared to the hundreds you'd have maintaining all the messages manually) and then you are able to maintain all your messages in the IDE without resorting to editing script whenever you want to change a message or the conditions under which a message applies.
Oh, and I almost forgot to mention, you can use a one-line call to the Replace function (I think that's a function available in VBScript -- I know I've used it in VB) to change text like "%PLAYER%" into the actual player name:
MsgSegment = Replace(MsgSegment, "%PLAYER%", PlayerName)
-
Sounds a bit complex, but if anyone's willing to help me figure out to make it, I'll go with that strategy.
Would this allow me to use bounds for several values of progress? I assume greater than 12 would be easy enough, but what about from 7 to 10?
Oh, and would it be possible to check some different values, like if I had a series of "go find the item" style quests, could I use different inventory items per message box? Like, test Progress and Ring?
-
I think there's a much easier way to do this. The VBScript functions "Eval" and "Execute".
Since VBScript is an interpreted language, rather than compiled, you can write code as a string and simply call Eval or Execute on it to have it run. For example:
dim a, b, c
b = 15
c = 10
a = "b > c AND c = 10"
msgbox Eval(a)
Will throw up a messagebox saying "True", since b is greater than c and c is equal to 10.
The upshot of this is that you can use this to write your conditions in the textbox. For example, instead of:
#COND 1=0
Oh woe is me, I have lost my wedding ring! Who are you? %PLAYER%? Can you help me find my ring, %PLAYER%?
#COND 1=1
You found it! Thank you so much.
#COND 1=2
Thanks again for finding my ring, %PLAYER%!
you could write:
#COND
dim PROGRESS
PROGRESS = ProjectObj.GamePlayer.InvQuantityOwned(1)
select case PROGRESS
case 0
msg = "Oh woe is me, I have lost my wedding ring! Who are you? %PLAYER%? Can you help me find my ring, %PLAYER%?"
case 1
msg = "You found it! Thank you so much."
case else
msg = "Thanks again for finding my ring, %PLAYER%!"
end select
And the code would be quite simple, something along the lines of:
Sub Player_OnSpecialFunction(SpecialObj)
If SpecialObj.FuncType = 1 AND left(SpecialObj.Value,7) = "#COND" + vbcrlf
dim tmp, msg
tmp = mid (SpecialObj.Value,8)
Execute(tmp)
ShowMessage(msg)
End if
End Sub
Sub ShowMessage(msg)
msg = Replace(msg,"%PLAYER%",PlayerName) 'Insert Players name where appropriate
msg = Replace(msg,"%GOLD%",ProjectObj.GamePlayer.InvQuantityOwned(2)) 'Insert current amount of gold where appropriate
'etc....
dim msgSpecial
Set msgSpecial = NewSpecialFunction
msgSpecial.funcType = 1
msgSpecial.Value = msg
ProjectObj.GamePlayer.ActivateFunction(msgSpecial)
End Sub
Something like that anyway. It may look confusing, but most of the code is just copy and paste. All you would need to change is the case numbers and the messages. Also, in VBScript, you can check for multiple values or ranges using case statements. For example:
select case PROGRESS
case 1, 7
msg = "Message for when PROGRESS = 1 or 7"
case 2 to 6
msg = "Message for when PROGRESS is either 2, 3, 4, 5, or 6"
case else
msg = "Message for when PROGRESS is greater than 7"
end select
You can also check for multiple variables, like you asked. However, you would need to use if statements in that case, instead of case statements. Or, if you want, you could use both:
#COND
dim PROGRESS, RING
PROGRESS = HostObj.GamePlayer.InvQuantityOwned(1)
RING = HostObj.GamePlayer.InvQuantityOwned(6)
select case PROGRESS
case 0
msg = "Message 1"
case 1
msg = "Message 2"
case 2
if (RING = 0) then
msg = "Message 3a (No Ring)"
else
msg = "Message 3b (Ring)"
case else
msg = "Message for all other occasions"
end select
-
That seems to be the best way so far, even though it may be a little more difficult than the previous one. This one seems like it'll give me the most flexibility, as far as checking multiple variables. It looks like I'd also be able to access some of the global script variables I have.
Oh, and if this is executed as script, I guess I can execute functions from it.
-
Gah! Bested by durnurd again ;)
Although I wonder if the internal script can affect local variables in the outer script. I suspect there might be a problem with the msg variable. You might have to make that a global variable instead of a local variable, and even then, I wouldn't be surprised if that didn't work.
-
Yeah, that didn't work.
Something's gotta be wrong... First off, the original message box appears, with the code in it. After that, I get an error pointing me to the line of Execute(tmp). Any ideas on what's going on, guys?
And don't worry bluemonkmn, I don't see durnurd making any game development kits :).
-
I tested the code I wrote and it worked perfectly. Of course, my test wasn't inside GameDev, so we can't really be sure which line is actually problematic unless you copy the code you wrote in the textbox into the actual script just to test what's going on in place of the Execute line. Then it will tell you exactly which line doesn't work.
But it does work with local and global variables just fine. I read a thing about the Execute and Eval functions (written by the guy who was responsible for putting those very functions in the VBScript engine) to make sure beforehand.
As for game development kits... well, not any visual game development kits, anyway.
http://ta-engine.sf.net
-
Ok, it seems like the problem is either at or very near to msg = "something". I get an "expected statement" error. Does that mean I'm leaving something out? Here's what I've got:
Sub Player_OnSpecialFunction(Func)
If Func.FuncType = 1 AND left(Func.Value,7) = "#COND" + vbcrlf Then
dim tmp, msg
tmp = mid (Func.Value,8)
dim PROGRESS
PROGRESS = HostObj.GamePlayer.InvQuantityOwned(1)
switch (PROGRESS)
case 0 to 10
msg = "Here's a message!" 'Problem apparently here
end select
ShowMessage(msg)
End if
End Sub
-
I had noticed that you replied to my message as I was editing it. I posted it, checked it, made a few changes to the code, then fixed the post, apparently after you already copied what I posted. I'm used to writing C-style code, so I used Switch instead of Select Case. Change
switch (PROGRESS)
to
Select Case PROGRESS
Also, you'll have to put something in, like BlueMonk said earlier, that changes the type of all of the special functions of type 1 that have #COND at the beginning to another type. This prevents the code from being displayed as a message. Something along the lines of
Sub Player_OnPlayInit()
dim tmp
for i = 0 to ProjectObj.MapCount
for j = 0 to ProjectObj.Maps(i).SpecialCount
set tmp = ProjectObj.Maps(i).Specials(j)
if tmp.FuncType = 1 AND len(tmp.Value) >= 5 AND left(tmp.value,5) = "#COND" then tmp.FuncType = 7
next
next
End Sub
-
Okay, the home stretch: two problems left.
1) It doesn't want to accept "case 0 to 10" as valid. It'll take "case 0", but the whole "to" thing throws it for a loop.
2) Is there some assortment of characters that will give me a line break? If I just use enter, it's like a new line in script, which breaks the string.
Other than that, this is finally working.
-
I went ahead and made a test project and ironed out a few kinks. Here's my final version:
dim PlayerName
PlayerName = "John"
Sub Player_OnSpecialFunction(SpecialObj)
If SpecialObj.FuncType = 7 AND len(SpecialObj.Value) >= 7 AND left(SpecialObj.Value,7) = "#COND" + vbcrlf Then
dim tmp, msg
tmp = mid(SpecialObj.Value,8)
Execute(tmp)
ShowMessage(msg)
End if
End Sub
Sub ShowMessage(msg)
msg = Replace(msg,"%PLAYER%",PlayerName) 'Insert Players name where appropriate
dim msgSpecial, old
Set msgSpecial = NewSpecialFunction
msgSpecial.funcType = 1
msgSpecial.Value = msg
old = HostObj.ScriptTimeoutSeconds
HostObj.ScriptTimeoutSeconds = 0
ProjectObj.GamePlayer.ActivateFunction(msgSpecial)
HostObj.ScriptTimeoutSeconds = old
End Sub
Sub Player_OnPlayInit()
dim tmp
for i = 0 to ProjectObj.MapCount - 1
for j = 0 to ProjectObj.Maps(i).SpecialCount - 1
set tmp = ProjectObj.Maps(i).Specials(j)
if tmp.FuncType = 1 AND len(tmp.Value) >= 5 AND left(tmp.value,5) = "#COND" Then
tmp.FuncType = 7
tmp.Flags = tmp.Flags OR 8 'INTFL_RAISEEVENT
end if
next
next
End Sub
HostObj.SinkObjectEvents ProjectObj.GamePlayer, "Player"
HostObj.ConnectEventsNow()
ProjectObj.GamePlayer.Play 16
And the code inside the special function:
#COND
dim FOUNDRING
FOUNDRING = ProjectObj.GamePlayer.InvQuantityOwned(0)
select case FOUNDRING
case 0
msg = "Oh woe is me, I have lost my wedding ring! Who are you? %PLAYER%? Can you help me find my ring, %PLAYER%?"
case 1
msg = "You found it! Thank you so much."
ProjectObj.GamePlayer.InvQuantityOwned(0) = 2
case else
msg = "Thanks again for finding my ring, %PLAYER%!"
end select
In the script, I changed the script timeout to 0 while showing the message so the script doesn't timeout if the player looks at it for more than two seconds. I also changed an IF to check for the right function type in OnSpecialFunction since it changes in OnPlayInit. I also took the liberty of adding a Raise Event flag to all of the messages so even if you forget to put them in during design time it will put them in during runtime. Finally, I added another IF in the OnSpecialFunction to make sure that the value is at least 7 characters long before trying to take the first 7 of them.
In the GUI code, I didn't do much. I just renamed the variable to FOUNDRING since that's all that variable can account for. I also automatically added 1 to it after displaying the initial "You found it" message, so that it will display the "Thanks again" message any subsequent times.
As for your line break, if you want a line break in the message, do something like:
msg = "First line of the message" + vbcrlf + "Second line of the message
Or
msg = "First line of the message" + vbcrlf + _
"Second line of the message
Which does the same thing but is easier to read. The vbcrlf stands for VB Carriage Return Line Feed, which is basically the fancy Windows way of saying New Line.
-
As for the case thing... looks like VBScript isn't as fancy as VB itself. The only thing you can do in VBScript is check for individual values or comma-separated lists of values:
select case x
case 1
case 2, 3, 4
case else
end select
If you want to check for something with more than a few values, I'd use an If-Else statement instead:
If x >= 0 AND x <= 10 Then
msg = "Message 1"
else if x > 15
msg = "Message b"
else
msg = "Message c"
end if
-
Are you sure you want this level of flexibility at the cost of writing script for every conditional message? I'm all for flexibility (which you'll see in SGDK2), but sometimes the complexity it introduces is just unwieldy :). So you gotta find a good balance. Of course, you can also have the best of both worlds. Use #CODE for scripted messages and #COND for simpler conditions.
-
That got it! It's finally doing everything we need. Thanks alot, durnurd!
And bluemonkmn, I realize this is going to make things a bit more complex, but now that it's done, the hard part's over. All it is now is copying a few lines and then adding all the crazy stuff we need to happen. I'm sure it'll be worth the effort in the end.