Holding Pattern

Wanted to update that I’m not dead. (Yay.)

Currently we’re waiting on the final Adam sprite to push a new release. But a few awesome things have happened in the meantime:

  • New background (campus buffet) was finished
  • Main theme song was finished
  • Bonus scenes for almost all of the guys are written

Once the sprite is in, it should be a quick process to export all of the layers out, replacing the current sketch placeholders I used to test. The music and background are already in the game, so should have a release out the same night. Yay.

(Also, yeah, this looks ridiculous, even with just the sketch placeholder. 😛 And I guess it will continue to do so until most of the sprites are replaced.)

I’m also in the early stages of assembling a behind-the-scenes book (PDF), which goes into a bit more detail than this blog about the early process and story of the game, how different characters developed, and compiling some of the artistic process behind the assets together. (Of course it will also include copious galleries of sprites and backgrounds.)

The plan is to offer such a book as a “bonus” you can pay for. (The game itself, of course, will always be free.) Thinking something like $5 for the soundtrack (and a steam key, if/when I port this over to steam), and $9 for the book?

The idea is that I want to raise some small amount of funds to commission more art for the game (like CGs), because there’s more I want to do there. Not sure how reasonable that (or those amounts) seem. I guess we’ll have to see.

Codes: Character Profiles

Another thing the game has (and will build on in the next release) is unlockable character profiles with some information about each character, in addition to some bonus features unlocked from them. As far as things I’ve posted here go, this is a fairly straightforward one, but I figured I’d share anyway.

First of all, I defined a block of data for each character’s information. In addition, because these are linked from the title screen via a sketched sprite, I included coordinates for the appropriate sprite.

python early:
# list in reverse order of rendering on title screen
real_profile_names = (
"nick", "hannah", "elliot", "nate", "lizb", "lizg", "adam", "juan", "jake", "dan", "james",
)
profile_profile_info = {
"adam": ("Adam", (950,340),
"Sophomore", "Biology", "August 15, 1986 (age 20)", "6'2\"", "California, United States",
"Adam James Prewitt",
"Profile text for Adam"),
"juan": ("Juan", (850,340),
"Junior", "Biology", "April 22, 1986 (age 20)", "6'0\"", "Costa Rica",
"Juan Rodrigo Salas Valverde",
"Profile text for Juan"),
…etc…
}

Next we actually put these on the title screen.

screen main_menu():
…Stuff…
fixed:
if "completed" in persistent.profiles:
imagebutton auto "images/title/leigh_%s.png" action Show("char_profile", chname="ashleigh") xanchor 0.0 yanchor 0.0 pos (650, 140)
imagebutton auto "images/title/leigh_%s.png" action Show("char_profile", chname="leigh") xanchor 0.0 yanchor 0.0 pos (850, 140)
imagebutton auto "images/title/steve_%s.png" action Show("char_profile", chname="steve") xanchor 0.0 yanchor 0.0 pos (950, 140)
imagebutton auto "images/title/robert_%s.png" action Show("char_profile", chname="robert") xanchor 0.0 yanchor 0.0 pos (1050, 140)
for profname in real_profile_names:
if profname in persistent.profiles:
imagebutton auto "images/title/" + profname + "_%s.png" action Show("char_profile", chname=profname) xanchor 0.0 yanchor 0.0 pos profile_profile_info[profname][1]

Next, we define the screen for character profiles. Note the additional features that I’ll get into shortly.

screen char_profile(chname):
tag charprofile
xpos 0
ypos 0
default pmode = "normal"
fixed:
add "images/sp/profilebg.png" xpos 0 ypos 0
if pmode == "best":
add "images/title/profile_" + chname + "best.png" xalign 0.5 yalign 1.0 xpos 175 ypos 720
elif pmode == "good":
add "images/title/profile_" + chname + "good.png" xalign 0.5 yalign 1.0 xpos 175 ypos 720
else:
add "images/title/profile_" + chname + ".png" xalign 0.5 yalign 1.0 xpos 175 ypos 720
if (chname == "james" and persistent.permacc_james) or (…other guys’ checks…):
add "images/title/profile_" + chname + "acc.png" xalign 0.5 yalign 1.0 xpos 175 ypos 720
if chname + "best" in persistent.profiles or chname + "good" in persistent.profiles:
imagebutton auto "images/sp/proftab_normal_%s.png" action SetScreenVariable("pmode", "normal") xanchor 0.0 yanchor 0.0 pos (375, 631)
imagebutton auto "images/sp/proftab_good_%s.png" action SetScreenVariable("pmode", "good") xanchor 0.0 yanchor 0.0 pos (375, 559)
if chname + "best" in persistent.profiles:
imagebutton auto "images/sp/proftab_best_%s.png" action SetScreenVariable("pmode", "best") xanchor 0.0 yanchor 0.0 pos (375, 480)
imagebutton auto "images/sp/profextra_%s.png" action Start("epilogue_bonus_" + chname + "1") xanchor 0.0 yanchor 0.0 pos (350, 0)
imagebutton auto "images/sp/profpermacc_%s.png" action ToggleField(persistent, "permacc_" + chname) xanchor 0.0 yanchor 0.0 pos (363, 171)
vbox xpos 500 ypos 30 xalign 0.0 xsize 480:
spacing 10
text "{size=40}" + profile_profile_info[chname][0] + "{/size}"
text "{size=25}Name: " + profile_profile_info[chname][7] + "\nYear: " + profile_profile_info[chname][2] + "\nMajor: " + profile_profile_info[chname][3] + "\nBirthday: " + profile_profile_info[chname][4] + "\nHeight: " + profile_profile_info[chname][5] + "\nHome: " + profile_profile_info[chname][6] + "{/size}"
text "{size=25}" + profile_profile_info[chname][8] + "{/size}"
vbox xpos 1250 ypos 30 xalign 1.0 xsize 400:
for k in sorted(profile_profile_info):
if k in persistent.profiles or ("completed" in persistent.profiles and k in ("ashleigh", "leigh", "steve", "robert")):
textbutton profile_profile_info[k][0] action [Show("char_profile", chname=k), SelectedIf(chname == k)] xalign 1.0
textbutton "Return to Main Menu" action Hide("char_profile") xalign 0.0 xpos 500 ypos 675

As you can see, the profile screen has a few unlockable features. The first feature is buttons that let you view alternate poses for the character image, depending on which ending you’ve completed with the given person. This is implemented simply, with a screen variable indicating the mode.

The second feature is the ability to access a bonus scene for the given character, once you’ve found their HEA ending. Just define a label with the appropriate name (epilogue_bonus_james1 for example).

The last feature is a toggleable item on the guy that will persist on their sprite during subsequent playthroughs. For example, if you get James’ best ending, you can toggle his enagement ring on or off, and have it show up whenever he appears in the game. The associated code for that builds off of our sprite code and simply adds a new check for the persistent variable, and a layer.

image james jamesnew = LiveComposite(
(266, 700),
(0, 0), "chnew/james/[spposes_data[james].pose]/base.png",
(0, 0), "chnew/james/[spposes_data[james].pose]/basehair.png",
(0, 0), "chnew/james/[spposes_data[james].expression].png",
(0, 0), "chnew/james/[spposes_data[james].pants].png",
(0, 0), "chnew/james/[spposes_data[james].shirt].png",
(0, 0), "chnew/james/[spposes_data[james].permacc].png",
(0, 0), "chnew/james/[spposes_data[james].extras].png",
)
    def execute_showsp(o):
…old code…
# Handle epilogue accessories
if who == "james" and persistent.permacc_james:
store.spposes_data[who].permacc = help_get_filepathsp(who, pose, "x", "ring")
…other guys' accessories…
else:
store.spposes_data[who].permacc = help_get_filepathsp(who, pose, "x", None)
…old code…

And… that’s it. Obviously, your profiles don’t have to be nearly this complicated. If you just want to show some basic information for each character, the screen and associated logic will be much simpler.

Dating mechanics

I’ve posted a bunch about the coding process of this game, and a bunch of WIP stuff for art and music, but I haven’t really yet talked about the game itself and its underlying mechanics.

One of the more interesting parts to design and build, to me, was the actual gameplay behind the four* different dateable guys, and I want to talk a bit about their paths, requirements, and how their characterizations impact their reactions to your decisions. 

Spoilers follow, of course, and I really recommend playing the game at least once without spoiling anything about the characters beforehand, because getting to know everyone is part of the point. Follow the cut to continue.

* Four?

Continue reading “Dating mechanics”

Sprites and Expressions

One thing my sprite artist (the awesome @stollcomics) had been doing was varying the head, as needed, in different expressions. For example, here’s a sample of (inked!) Adam heads, and I think it makes for a much more expressive sprite than the usual “static head and moving facial features” thing.

However! In the process I think we also discovered why head movements aren’t a normal thing in visual novels – because it takes really long to draw and ink so many heads, which multiplies out even more when you want hair and facial hair variations. (We were expecting to have a final Adam sprite, colors and all, this weekend, but instead he only got around 70% done with inks.)

Given that, I think we’re going to be going back to the “usual” visual novel style with a static head and moving facial features. It won’t look as good as these, but it’ll be a heck of a lot easier on him, and generally means this process won’t take as long given the 13 other sprites we have to get through.

It’s particularly unfortunate that a lot of this awesome art isn’t going to see the light of day (other than this post, I suppose), but I’m really glad that we ran into this early in the process (on the first sprite) instead of after doing a couple of simple ones and then having to go back and redo those, as well.

Learning experiences, I guess!

(This also means, of course, that the next game release is delayed for a couple weeks while we redo the expressions and get the new sprite colored.)

Todo List #5

I’m expecting to have the finished Adam sprite this weekend (and certainly next week, if not), so the next release will be focused on getting him into the game. (Essentially, replacing every “show adam” command in the game with either “showsp adam” or “showexp adam” and testing the results. There’s almost 1100 such commands, so I have my work cut out for me; thank god for lint.)

  • Replace Adam sprites in game with final sprite.
  • Continue to condense dialogue blocks to reduce number of clicks. 
  • Add additional features to character profiles.
  • Allow MC to pay for food in group outings.

If the theme song gets finished, I’ll stick it in as well. Might be a little jarring to have music only on the title screen, but that’s what the sound settings in preferences are for, I guess. 🙂

Release #5

It’s been a big week since the last release, mostly filled with things that aren’t in the game (or aren’t apparent) yet. But it’s exciting to see so much external progress, all the same.

Dumping a new release anyway mostly for the bug fixes and character profiles. (More coming in that department.)

Version: v0.11011

As usual, you can get it on the download page.

Changes in this release:

  • New sprite system is in place (no apparent effects yet)
  • Leigh/Ashleigh are now even more like real people
  • New scenes with Leigh, Ashleigh, and Steve
  • Characters now react differently to certain names
  • Characters now react slightly differently to a lack of studying
  • Character profiles are now filled in
  • New achievements
  • Achievements now show a notification when gained
  • Minor bug fixes

Stats for this release: 27,584 dialog blocks, 215,801 words, 1,129,531 characters

Sprites: More Adam Sketches, James Preview

More from the talented hands of @stollcomics. Here’s Adam looking good. 🙂 I think his sprite design is done at this point, and he’ll soon get inked and then colored.


Here’s a preview of a couple iterations in design on James.

The second is the more recent one. I think he’s going to be repositioned, and have some slight tweaks, but that’s the general idea.

Codes: Sprite expression changes

Last time, we added a custom showsp command to easily show sprites that obey game settings automatically. But that can be tedious to use if all you want to do in a scene is change a character’s expression when they’re already onscreen. Cue showexp!

A couple of changes to the old code are necessary. First of all, we need to store the raw names for the clothing (you’ll see why in a moment).

python early:
class CharPoses:
pose = ""
pants = ""
shirt = ""
expression = ""
extras = ""
oldclothes = ""
basepantsstr = ""
baseshirtstr = ""
baseextrasstr = ""
def execute_showsp(o):
…same as before…
if not who in store.spposes_data:
store.spposes_data[who] = CharPoses()
store.spposes_data[who].pose = pose
store.spposes_data[who].baseshirtstr = shirt
store.spposes_data[who].basepantsstr = pants
store.spposes_data[who].baseextrasstr = extras
store.spposes_data[who].shirt = help_get_filepathsp(who, pose, "s", shirt)
store.spposes_data[who].pants = help_get_filepathsp(who, pose, "p", pants)
store.spposes_data[who].expression = help_get_filepathsp(who, pose, "e", expr)
store.spposes_data[who].extras = help_get_filepathsp(who, pose, "x", extras)
…same as before…

Next, we define the parser and lint for showexp. Note that we’re allowing changes to the pose, as well, so this can be called as either showexp adam happy or showexp adam happy crossed.

python early:
def parse_showexp(lex):
who = lex.simple_expression()
expr = lex.simple_expression()
if lex.eol():
return (who, None, expr)
pose = lex.simple_expression()
return (who, pose, expr)
def lint_showexp(o):
who, pose, expr = o
if len(o) != 3 or expr is None:
renpy.error("Invalid showsp declaration")
return
if not who in showsp_valid_combos:
renpy.error("Invalid person " + who)
return
if pose is None:
# don't know the pose so check across poses
# may fail if a given expr only exists for one pose
# but we can't track current pose state
hasExpr = "e_" + expr in showsp_valid_combos[who]["!any"]
if not hasExpr:
for pose in showsp_valid_combos[who]:
hasExpr = "e_" + expr in showsp_valid_combos[who][pose]
if hasExpr:
break
if not hasExpr:
renpy.error("Invalid expression " + expr + " for " + who)
else:
if not pose in showsp_valid_combos[who]:
renpy.error("Invalid pose " + pose + " for " + who)
return
if not "e_" + expr in showsp_valid_combos[who][pose] and not "e_" + expr in showsp_valid_combos[who]["!any"]:
renpy.error("Invalid expression " + expr + " for " + who + pose)

Note the documented limitation in linting. Since we don’t always have the pose information, if you have an expression that exists only for one pose, this will be missed by linting. (So make sure all expressions exist in some base form, and override them on a per-pose basis as necessary, to prevent this.)

Finally, the actual execute command, where we use the information we’d previously stored in the new fields:

    def execute_showexp(o):
who, pose, expr = o
if not who in store.spposes_data:
renpy.error("Invalid person " + who)
if pose is not None:
if store.spposes_data[who].pose != pose:
# force all other components to re-render as necessary when pose changes
store.spposes_data[who].shirt = help_get_filepathsp(who, pose, "s", store.spposes_data[who].baseshirtstr)
store.spposes_data[who].pants = help_get_filepathsp(who, pose, "p", store.spposes_data[who].basepantsstr)
store.spposes_data[who].extras = help_get_filepathsp(who, pose, "x", store.spposes_data[who].baseextrasstr)
store.spposes_data[who].pose = pose
else:
pose = store.spposes_data[who].pose
store.spposes_data[who].expression = help_get_filepathsp(who, pose, "e", expr)
renpy.register_statement("showexp", parse=parse_showexp, execute=execute_showexp, lint=lint_showexp)

This will probably also fail if a particular shirt, pants, or extra exists only for one pose and you change pose as part of the command. Again, to be safe, make sure everything a character could be wearing exists at some form at the base level, or just don’t change poses around when using the command.

In any case, now you can more easily carry on a conversation without having to remember what a character was wearing in the same scene (or even where they’re standing, which is an improvement over the basic game’s show command, as well).

showsp adam sides greent jeans happy
adam "This is only a test. How do I look?"
showexp adam worried
adam "Does this look okay?"
showsp adam crossed bluet slacks curious jacket left
adam "Now I'm over here, and in blue. Huh."
showexp adam confused
adam "Strange."
showexp adam default crossed
adam "Oh well."
showexp adam grin sides
adam "It's nice to meet you anyway."

Yay! One last thing: Calling showexp before showsp will probably do nothing, since changing variables around while a sprite is offscreen has no effect. So if your script has a lot of jumps in it, you probably want to lead each label with a showsp for all of your characters, again, just to be safe.