Your example is overly specific.
As is any efficient code designed to works as "system". Even a framework would be overly specific... to the way its author see the system being implemented and the engine for which it's made.
It can't be implemented the exact same way, but if you want a place to store girl info then that's easy.
Once again you missed the point. The goal isn't to store the girls data, but to design the whole data system in a way that is both robust, easy to handle and easy to use. And this radically change the algorithm used, as seen from the code point of view.
Say someone says your game is good, or they say that it was a great idea, or that it was well made; each of these are a gross simplification of all the work done. Not just the time end effort, but it wasn't one good idea, it was a serious of choices and design consideration that leads to success, not just one thing.
Yeah, saying that "it's good" make it feel like it was easy to do.
It's like old school movies animators. For Shreck, by example, some of them jumped in mud pound just to have a better understanding regarding how it react, in order to do it right on screen. There's also the Disney golden age, with the studios full of living animals to help the animator to create believable animorphs.
So, yeah, "it's good... If you knew what I had to do for this to be 'good'..."
even if its just a mini-engine, such as its just a mini game mechanic for a sex scene, the issue I have seen with them that could make them look 'specialized' is that in order to operate they need a character datatype to use.
Whatever you design the data first and the system once it's done, or the opposite, the algorithm you'll get will always be 100% specialized for those data.
With its stores, Ren'Py offer an access to multiple data spaces from a single code space. Almost no games use this approach, but it create a level of data isolation that radically change the way you'll deal with them, and therefore the algorithm you'll use.
it seems what you and anne O'nymous are talking about is the age old talk of, identifying and isolating the 4 levels of a system's abstraction, from raw code, to how its designed (assuming OOP paradigm), the core goal and the philosophy of its design.
One of, if not the, the base of algorithm design: do you want something easy to implement, or something easy to use ?
Choice wisely, you can't have both. The first one will not be easy to use, and the second not be easy to implement. And of course, "easy" will totally depend of the language.
What I don't see with ren'py games (and feel to correct me on this anne O'nymous), is serious implementation of custom and complex data types.
I agree. Not that it's impossible, but it goes above the knowledge of most coders on the scene. They are mostly amateurs, most professionals ones goes for engines that feel more professionals. Therefore they tend to stay at a level of data types they fully understand.
And in fact I think that even professionals stay to a "low level" of data type, because they can afford to. I'm rewriting my variable viewer, and I'm still unsure if I want to use objects that would handle everything, or stay on the list of strings approach I initially used.
For the first version such simplicity was a need, because at this time I was still discovering Ren'Py and it's way to deal with data. So, keeping it simple was the best way to not being stuck at some point. But now that I have 3 years of use proving me that it works fine that way, I wonder if I want to make my life harder by using a complex data type that I'll only use once, or harder by dealing with a basic data type that will need more attention.
One of the approach will be a bit less hard than the other, and I highly suspect that it's the basic data type.
This apply even more when it come to games released by updates. There's not much possibilities:
- You dedicate one year to the design of your data type, releasing nothing, not even effectively working on your game ;
- You pass half your time between updates only working on the design of the data type you now need, meaning less frequent updates ;
- You stay on basic data type, and lost few hours updating the game because of this.
Well, seem to me that the last one is the less worse choice. Not that the two others are bad ; the first one should be the chosen one. It's more that you want return on your game, and no one feel like loosing one year designing the perfect data type, to abandon the game after one month because everyone find it ridiculously stupid.
What fall back to what you said at first, "it's good"... Would I have known that the guy who blindly put everything in an array, and try to remember what index have what value, will make a game that you find "marvelous", I would have lost less time.
My mod for superPowered is "good"... Do people want to know how many aspirin tubes I emptied searching the best way to make it being "good" ? I past near to one week searching a working way to transform a dialog line into a notification without having to touch the original code, and it's "good"... Man, it's both fucking genius, fucking ridiculous and fucking dirty.
And the same apply to any coder on the scene, even the worse ones. Yeah, their code is ugly, dirty, and perhaps even broke. But they past one week searching for it, so no, it's not "good". For their level of knowledge it's way more than that.
When I say, custom and complex, I mean something as simple as having a object class made to represent an idea, such as being a container for data, and then simply making different instantiations with different data that you can swap in and out to make new game content.
What, paradoxically, would be so easy with Ren'Py due to Python duck typing.
Take something basic: MC positive actions increase the girl love, MC negative actions decrease it.
Add it a bit of complexity: The more she's in love, the less she can be "more in love", while the more she's in love, the more she can be deceived.
Python duck typing make the code of your game be fuckingly easy to deal with. Redefine addition:
Code:
if value > self.weightLimit
value = value + ( add / self.weight )
else
value = value + add
if value > self.maxValue:
value = self.maxValue
and subtraction:
Code:
if value > self.weightLimit
value = value - ( sub * self.weight )
else
value = value - sub
if value < self.minValue:
value = self.minValue
Be inventive, the value is internally a float, for a better granularity, but an integer when looked at.
Design your Girl class in order to use this refactored integer as love value, with the correct limit and weight depending of the girl:
Code:
love = WeightedInt( startValue, weightLimit, weight, min, max )
And it's done. Starting now you don't need to care about the girl preference and the actual value.
Add something, subtract something, test the value, not just "as if all the girls had the same weight limit and boundaries", but as if there were no weight and boundaries:
Code:
label whatever( girlObject ):
[...]
menu:
"do something stupid":
girlObject.love -= 2
"do something romantic":
girlObject.love += 2
For one girl it will lead to a -5, for another a -1, whatever, you don't need to care about this anymore ; you don't even need to be aware of this when writing that part of the code.
Perhaps that a girl that isn't enough in love will not care at all. Perhaps that there's even a girl who love MC's stupidity and that her love increase each time there's a
love -= x
, while she hate romantic shit and so her love decrease each time there's a
love += x
with x > 5 by example.
All this isn't the concern of the code you're working on right no. Your algorithm say that this action decrease the love a little more than usual, it's what will happen. This even if this "little more" happen to be a big change because the girl to who it happened have been so deceived by the MC, or if it lead to an increase of the love because the girl is crazy. This isn't your problem at this moment, so you don't care about it here.
Of course, this is already possible with regular integers, but to the price of a function call that will pass through the same case-like algorithm you hide in the refactored integer. A function that would have to firstly discriminate between each girl, because they don't have the same limits, and that would be less easy to use:
Code:
label whatever( girlObject ):
[...]
menu:
"do something stupid":
$ decreaseLove( girlObject, 2 )
"do something romantic":
$ increaseLove( girlObject, 2 )
And in top of that, this will constantly remind you that the values are subject to changes, what, as I said, isn't your concern here. It will make you overthink the value you need to use, in order for it to reflect the right reaction, while what you need to know is in fact the intensity of the expected reaction, not the reaction itself.
For me, modularity is, being able to tell the engine what scene to load up, all it needs to do is bring the scene to life.
Personally, and thanks to Python duck typing, I would goes further regarding the modularity. And after what I wrote above, I obviously talk about including the value changes:
Code:
label whatever( girlObject ):
$ girlObject.action = "anal"
menu:
"faster":
girlObject.climaxStep += 1
"harder":
girlObject.climaxStep += 2
"slower":
girlObject.climaxStep -= 1
"gentler":
girlObject.climaxStep -= 2
$ girlObject.climax += girlObject.climaxStep
A girl who don't care about anal would keep the value as defined. A girl who hate anal would see the value inverted. And a girl who like anal would have the value increased.
And then on top of that,
girlObject.action
and
girlObject.climax
would be use to describe the scene. While
girlObject.climaxStep
would be used to describe the girl reaction.
But looking at that wouldn't give you the effective algorithm used by the sex system. It will falsely make you believe that all girls have the same reaction to the same action.
As for the code for the actual engine itself, For me at least, its not possible to just have the engine contained in a single class file. It would be nice and readable, [...] the list of things they want to achieve would bloat the file into a huge wall of text, so the engine becomes a collection of ideas and mechanics.
It's where Ren'Py is superior, on its ease of use, to most other game engines.
- Define the different refactored integers you need ;
- Define a generic girl class that use them ;
- Define a system that don't care about the particularities ;
- Use Ren'Py dynamism.
I already gave an overview of the three first steps. The last one is as simple as:
Code:
image sexScene1 = "images/scene/sex/[girlObject.name]/[girlObject.action]/[girlObject.climax]_1.jpg"
image sexScene2 = "images/scene/sex/[girlObject.name]/[girlObject.action]/[girlObject.climax]_2.jpg"
image sexScene3 = "images/scene/sex/[girlObject.name]/[girlObject.action]/[girlObject.climax]_3.jpg"
[...]
label whatever( girlObject ):
while not MCcummed:
scene sexScene1
girlObject.sayer "[girlObject.sexMoaning1]"
mc "[mcObject.grunt1( girlObject.action )]"
scene sexScene2
girlObject.sayer "[girlObject.sexMoaning2]"
mc "[mcObject.grunt2( girlObject.action )]"
scene sexScene3
girlObject.sayer "[girlObject.sexMoaning3]"
mc "[mcObject.grunt3( girlObject.action )]"
menu:
"apology" if girlObject.climaxStep < 0:
[...]
"faster":
$ girlObject.climaxStep += 2
[...]
"change position":
menu:
"missionary" if girlObject.action != "missionary":
$ girlObject.action = "missionary"
"anal" if girlObject.action != "anal":
[...]
"change girl" if multiplePartners:
menu:
"choose [girl1.name]" if girl1.involvedInSex:
$ girlObject = girl1
[...]
$ girlObject.climax += girlObject.climaxStep
And it's done, really. With this (and the three first steps) you have a working sex system in Ren'Py.
It will care by itself to select the images corresponding to the actual action for the actual girl with the actual pleasure intensity she have. And it will care by itself to present the choices corresponding to the actual context. This will the object will care to select the right dialog lines accordingly to the context, and to update the girl climax accordingly to the girl preferences.
Of course, it's a limited sex system. All actions would feel more or less the same, but it's already the case in many games that have a different code for all the different actions of all the different girls and for all the different pleasure intensity they can have. But globally speaking, with Ren'Py the system wouldn't need to be more complex than that. And it's what I said above, either something easy to implement but that will be hard to use, or something easy to use but that will be hard to implement.
another issue is, for projects I've help with, the sex engine isn't isolated, it starts to become interconnected to other systems. Say its a dating sim, and bit by bit you unlock a character's kinks or willingness. Well you need to keep track of the player's data some how, either with a data class for the progress (of the player), or a messy class that keeps track of all the data, or my preferred, a class for each character to keep track of data individually.
Or a dynamic object as hub, as I implied in my previous answer. If you design it correctly, the data are stored the way you want, but from your point of view all available from the same place.
Code:
class Girls( object ):
[...]
def __getattr__( self, name ):
if name in self.__handledGirls:
self.__current = name
return self
raise AttributeError( "Oops" )
[...]
@property
def love( self ):
return self.__girlsObjects[self.__current].love
# or
return store.love[self.__current]
# or
return self.__love[self.__current]
# or
return getattr( store, self.__current+"_love" )
# or whatever else
And then
girl.anna.love += 1
whatever how you'll store the data, whatever how many girls you have, and whatever their names. This even if you suddenly decide to add few girls or remove one.