Night.png);">
Apprendre


Vous êtes
nouveau sur
Oniromancie?

Visite guidée
du site


Découvrir
RPG Maker

RM 95
RM 2000/2003
RM XP
RM VX/VX Ace
RM MV/MZ

Apprendre
RPG Maker

Tutoriels
Guides
Making-of

Dans le
Forum

Section Entraide

Sorties: Dread Mac Farlane - Complet / Sorties: "Dread Mac Farlane", (...) / Tutos: Checklist de la composition (...) / Sorties: Dread Mac Farlane - episode 8 / Sorties: Dread Mac Farlane - episode 7 / Chat

Bienvenue
visiteur !




publicité RPG Maker!

Statistiques

Liste des
membres


Contact

Mentions légales

560 connectés actuellement

29499717 visiteurs
depuis l'ouverture

10292 visiteurs
aujourd'hui



Barre de séparation

Partenaires

Indiexpo

Akademiya RPG Maker

Blog Alioune Fall

Fairy Tail Constellations

Kingdom Ultimate

Tashiroworld

Le Comptoir Du clickeur

RPG Maker - La Communauté

Tous nos partenaires

Devenir
partenaire



Messages postés par dagothig
Nombre de messages référencés sur Oniromancie (non supprimés): 8

Aller à la page: 1

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 21/02/2023 à 14:27:44. (8 messages postés)

Hehe, oui quand même pas mal x)
Au final j'avais surtout besoin de rant un moment sur l'expérience de vouloir rajouter des choses dans le script!

Normalement c'est mon dernier caca-nerveux-de-texte-sur-de-la-programmation-pas-révisé avant un bout je crois

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 21/02/2023 à 04:26:30. (8 messages postés)

Je repasse pour dire coucou et faire un autre dump d'un ajout à l'engin mal foutu:

Donc par orgueuil mal placé j'essaye de limiter plutôt les plugins que je tire - J'en utilise quelques uns, surtout de quand je venais de commencer le projet et que je ne connaissais pas trop comme c'est monté, mais sinon je fais surtout les trucs à la mitaine par principe.

Tout ça pour dire, j'ai fais un indicateur d'ordre des tours avec aucune fonctionnalité qui marche à moitié :^)



Ça m'a pris un moment tout de même, parce que le code de windowing de RMMV est à mon sens complètement débile, mais bon... j'ai pas finis de dire d'la shit à propos de l'engin.

J'ai finis par réaliser que le système s'attend à ce qu'on fait une classe qui hérite de Window_Base (directement ou indirectement), et par la suite il faut que la scène instancie la fenêtre pour l'inclure dans son affichage MAIS... ce n'est pas la scène qui gère les mises à jours! Ce n'est pas non plus un update loop! Enfin si mais indirectement... Plutôt dans la vanille de l'engin il faut que partout où on pense que quelque chose de la fenêtre a changé faire un appel de refresh x).

Alors j'introduis un fenêtre que j'ai nommé (avec beaucoup d'originalité...) Window_BattleOrder, qui est le machin qui s'occupe d'afficher une petite icône de chaque acteur avec des icônes pour chaque action. Comme j'ai aussi pris la décision louche de ne pas toucher à la résolution par défaut qui est tout minuscule (... 816x624 je crois? enfin c'est la résolution qui match la taille des maps par défaut), j'ai pas énormément d'espace pour tout afficher, alors j'ai fais de l'usage "judicieux" (les quotes travaillent fort) de l'espace.

Alors donc, notre fenêtre c'est un Window_Base simple qui est la hauteur d'une icône + un peu de padding:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
Window_BattleOrder.prototype = Object.create(Window_Base.prototype);
Window_BattleOrder.prototype.constructor = Window_BattleOrder;
 
// On va overlap les icônes pour sauver de l'espace!
const drawOffset = Math.round(Window_Base._iconWidth * (2/3));
 
override(Window_BattleOrder.prototype,
    function initialize(initialize, x, y) {
        initialize.call(this, x || 0, y || 0,
            Graphics.boxWidth,
            Window_Base._iconHeight + this.standardPadding() * 2);
        this.opacity = 0;
    },
    function standardPadding() {
        return 8;
    },
    ...
    );
 



Et on a une fonction refresh qui est appelée lorsque que quelqu'un quelque part décide qu'on doit se réafficher. Afficher des icônes c'est déjà supporté alors c'est gratuit... mais afficher la face des acteurs il faut mettre en place quelque chose. En pratique il y a les acteurs de base et les enemis de base que je veux supporter, mais il y a aussi les acteurs-enemis qui ont besoin d'être correctement flippés (vu qu'ils utilisent les mêmes assets que les acteurs):

Notre intention c'est d'avoir un beau

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
 
    override(Window_Base.prototype,
        // Battler peut être n'importe quel type de battler enemi ou allié
        function drawBattlerIcon(_, battler, x, y) {
            // Go brrrrrr
        });
 



Comme on a aussi juste 32x32 pixels d'espaces, on va juste afficher un tout petit bout de chaque image, qui va devoir être judicideusement choisi pour qu'on comprenne c'est qui. Il se trouve que si on creuse assez dans Window_Base que ce qui est affiché c'est son this.contents qui est un "Bitmap", et qui nous offre la fonction

Portion de code : Tout sélectionner

1
2
3
4
5
 
// Bitmap go blllllllllllllllllllllt
// sx/sy/sw/sh c'est le rectangle de l'image source, dx,dy,dw,dh c'est le rectangle de l'image cible
Bitmap.prototype.blt = function (source, sx, sy, sw, sh, dx, dy, dw, dh) {
 



Et donc c'est parfait! Si on a un battler donné, on peut distinguer les cas à l'aide isActor, et j'ai introduis actorSprite sur les enemis pour cibler ceux qui sont affichés comme un acteur:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
 
const data = battler.isActor() ? battler.actor() : battler.enemy();
let bitmap;
 
if (data._iconSrc) {
    bitmap = ImageManager.loadBitmap(data._iconFolder, data._iconSrc);
} else if (battler.isActor() || data.actorSprite) {
    bitmap = ImageManager.loadSvActor(battler.battlerName());
} else {
    bitmap = ImageManager.loadSvEnemy(battler.battlerName());
}
 



Faut pas trop faire attention au _iconSrc, j'ai un enemi qui est "Table dans les airs" et que son sprite c'est juste une ombre, alors j'ai ajouté du support pour dire une image arbitraire.
Sauf que oops! Ça implique que des fois drawBattlerIcon est appelé pour une image qui n'est pas en mémoire, alors pour pas se faire chier on se fait un petit

Portion de code : Tout sélectionner

1
2
3
4
5
6
 
// Ça shrekt complètement la priorité d'affichage hypothétiquement mais huh, c'est fiiiiiiine.
if (!bitmap.isReady()) {
    bitmap.addLoadListener(() => this.drawBattlerIcon(battler, x, y));
}
 



Donc une fois qu'on a notre bitmap, il suffit de lui dire où afficher! J'ai seulement réalisé la semaine dernière le concept des metas, alors j'ai tout un setup de regex qui spot des <<aaa_...>> dans les notes, et donc j'ai rajouté un

Portion de code : Tout sélectionner

1
2
3
4
 
// Les regexp c'est parfaitement lisible.
var battlerIconRegexp = /\<<aaa_icon *(\d+)? *(\d+)? *(\d+)? *(\d+)? *([-_/\w]+)?\>>/;
 



Et durant le onLoad du DataManager, je vérifie si c'est les $dataActors ou les $dataEnemies, et je fais le matching:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
var original_onLoad = DataManager.onLoad;
DataManager.onLoad = function (object) {
    original_onLoad.call(DataManager, object);
    if (object === $dataActors) {
        for (const actor of object) {
            if (!actor)
                continue;
            const note = actor && actor.note || "";
            const iconMatch = note.match(battlerIconRegexp);
            if (iconMatch) {
                window.iconMatch = iconMatch;
                actor._iconX = parseInt(iconMatch[1]) || 0;
                actor._iconY = parseInt(iconMatch[2]) || 0;
                actor._iconW = parseInt(iconMatch[3]) || 0;
                actor._iconH = parseInt(iconMatch[4]) || 0;
                actor._iconSrc = iconMatch[iconMatch.length - 1] || null;
            }
        }
    }
};
 



Ce qui fait que si on revient dans drawBattlerIcon, on peut utiliser aaa_icon pour les sx/sy/sw/sh, et déléguer le travail de trouver comment afficher l'icône à dagothig-qui-doit-rajouter-une-note-sur-100%-des-acteurs-et-enemis:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
if (Number.isFinite(data._iconX)) {
    sx = data._iconX || 0;
    sy = data._iconY || 0;
    sw = data._iconW || w;
    sh = data._iconH || h;
// J'ai mentis, pour les acteurs j'ai hardcodé une valeur qui marche pour les battlers par défaut.
} else if (battler.isActor() || data.actorSprite) {
    sw = battler.isActor() ? w : -w;
    sh = h;
    sx = 12;
    sy = 8;
// J'ai encore menti, pour les enemis j'ai un algo louche qui marche mal.
} else {
    const tw = battler.enemy().tw || bitmap.width;
    const th = battler.enemy().th || bitmap.height;
    const widthRatio = Math.max(tw, w) / w;
    const heightRatio = Math.max(th, h) / h;
    const ratio = Math.min(Math.min(widthRatio, heightRatio), 2);
    sx = (tw - sw) / 2;
    sy = (th - sh) / 4;
    sw = w * ratio;
    sh = h * ratio;
}
 



Vous remarquerez le sneaky "sw = battler.isActor() ? w : -w;", c'est pour qu'on supporte de flipper l'image arrivé à blllllllllllllllllllllting:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
 
if (sw < 0) {
    // C'est tellement laid, si quelqu'un connait une meilleure manière, je suis tout ouïe.
    sw = -sw;
    x = -(x + w);
    this.contents._context.scale(-1, 1);
    this.contents.blt(bitmap, sx, sy, sw, sh, x, y, w, h);
    this.contents._context.scale(-1, 1);
} else {
    this.contents.blt(bitmap, sx, sy, sw, sh, x, y, w, h);
}
 



Maintenant qu'on a un drawBattlerIcon, on peut donc faire notre refresh de Window_BattleOrder au complet! Il suffit de passer à travers les battlers qui ont des actions qui s'en viennent... les huuuuuhs... Si on fouille dans BattleManager on trouve... _actionBattlers ? qui exclut l'acteur qui agit actuellement, donc mettons qu'on les fout ensemble et puis -

Portion de code : Tout sélectionner

1
2
3
4
 
// Tada
let battlers = [BattleManager._subject].concat(BattleManager._actionBattlers);
 



Et ensuite puisque c'est un canvas qui affiche, chaque fois qu'on fait blt ça rajoute par dessus les images déjà affichées, alors comme je veux que ça affiche comme un paquet de carte un peu offset, il faut qu'on affiche les actions en ordre inverse, avec l'acteur en dernier. Ce qui est cool, parce que ça veut dire qu'on doit d'abord calculer la largeur, puis on va à l'envers, pour aller ensuite à l'endroit et ça m'a pris un peu trop de temps à faire marcher en fait:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
function refresh() {
    this.contents.clear();
 
    let x = 0;
    let battlers = [BattleManager._subject].concat(BattleManager._actionBattlers);
    for (const battler of battlers) {
        // Comme _subject existe pas toujours, battler existe pas toujours :)
        if (battler && battler.canMove()) {
            // Compute the space necessary
            let w = (battler._actions.length + 1) * drawOffset;
            let subx = x + w - drawOffset;
 
            // À l'envers!
            for (let i = battler._actions.length - 1; i >= 0; i--) {
                const action = battler._actions[i];
                const item = action.item();
                // 16  c'est un carré vide gris, pour les actions qui ne sont pas encore décidées
                this.drawIcon(item && item.iconIndex || 16, subx, 0);
                subx -= drawOffset;
            }
 
            this.drawBattlerIcon(battler, subx, 0);
 
            x += w + Window_Base._iconWidth - drawOffset + this.standardPadding();
        }
    }
}
 



On a notre fenêtre! Il suffit maintenant de la filer dans Scene_Battle et BattleManager:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
override(BattleManager,
    function setOrderWindow(_, orderWindow) {
        this._orderWindow = orderWindow;
    });
 
override(Scene_Battle.prototype,
    function createLogWindow(createLogWindow) {
        createLogWindow.call(this);
        // Faut décaler le log pour faire de l'espace!
        this._logWindow.y = Window_Base._iconHeight + 16 - this._logWindow.standardPadding();
        this._orderWindow = new Window_BattleOrder();
        this.addWindow(this._orderWindow);
    },
    function createDisplayObjects(createDisplayObjects) {
        createDisplayObjects.call(this);
        BattleManager.setOrderWindow(this._orderWindow);
    });
 



Et maintenant il ne reste plus qu'à faire que chaque fois qu'on commence à choisir une action, ou qu'on annule, ou que quelqu'un fasse quoi que ce soit, on appelle BattleManager._orderWindow.refresh(). Ezpz!

Right? Right.

Premier truc, c'est que je sais que les actions ont une influence sur l'ordre, alors il doit bien y avoir un endroit où le manager calcule l'ordre, et si on fouille pour speed, on se fait chier pour un bout avant de finalement trouver BattleManager.makeActionOrders! Donc quand ça c'est appelé, on rafraichit notre fenêtre:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
 
override(BattleManager,
    function makeActionOrders(makeActionOrders) {
        makeActionOrders.call(this);
        this._orderWindow && this._orderWindow.refresh();
    });
 



Et là on remarque que c'est appelé seulement au début du tour, et pas durant le choix des actions, donc les actions "rapides" ne sont pas calculé pendant l'input mmmh. Mais si on déclare qu'en fait quand on choisi une action ça calcule l'ordre, ça devrait bien se passer!? Il se trouve que choisir une action correspond à assigner l'objet d'un Game_Item??? Parce que les "items" comme on penserait c'est juste un objet de données et ne fait pas partie du modèle des classes??? Sure thing buddy:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
 
override(Game_Item.prototype,
    function setObject(setObject, item) {
        setObject.call(this, item);
        BattleManager.makeActionOrders();
    });
 



Et on s'approche fort! Pendant un moment je pensais arrêter là, mais comme personne "clear" les actions quand on retourne dans les menus, les actions restent là et c'est ... laid. Alors bon, si on continue à fouiller, on finit par réaliser que BattleManager fait un genre de modèle de forward/backward dans le flot des menus, et cibler *juste* le fait de rentrer dans le menu de sélection d'acteur n'est pas si facile. Sauf, si on brise les responsabilités (repsonsabili-quoi?) et qu'on fout le refresh drette dans le Window_ActorCommand:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
 
override(Window_ActorCommand.prototype,
    function activate(activate) {
        activate.call(this);
        // On croirait que c'est le seul endroit qui fait des bris de responsabilités comme ça, mais non, le modèle de RMMV vient tout brisé out-of-the-box!
        const action = this._actor && this._actor.inputtingAction();
        action && action.setItemObject(null);
    });
 



Et le dernier petit accroc visuel à mon sens c'est qu'au fur et à mesure que le tour se déroule les actions ne disparaissent pas une-à-une. Il se trouve cependant que dans le traitement des tours, à la fin d'un action, le BattleManager (ce bon vieux Objet-Dieu) s'occupe de retirer à l'acteur son action courante, et donc on peut s'y faufiler:

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
 
override(Game_Battler.prototype,
    function removeCurrentAction(removeCurrentAction) {
        removeCurrentAction.call(this);
        BattleManager._orderWindow && BattleManager._orderWindow.refresh();
    });
 



Whew. Et si on met tout ensemble (encore une fois, je fouts tout dans un script js qui tente de rivaliser mes pires erreurs de jeunesse), on a donc un indicateur de tour qui... fonctionne.

Je vais m'arrêter là, quand j'aurai du courage je pourrai vous faire une update sur les prochains segments, et sur les autres ajouts de plus en plus creux et louches!

Posté dans Forum - [RPG Maker VX] Calculer efficacement pour que le jeu ne rame pas

dagothig - posté le 21/02/2023 à 02:53:27. (8 messages postés)

Salut - Je ne sais pas si tu as encore le problème, mais il y a deux-trois trucs que tu pourrais peut-être essayer -

D'abord valider si c'est bien les changements d'items qui causent le lag. Si tu retires les changements d'item de ton event, ça suffit à se débarasser du lag? J'en devine que l'event lié ici roule en parallèle et le nombre d'item est généralement à 0 sauf dans certains cas? Un test à faire serait alors de ne faire les manips d'objets que si les variables gain_bataille_x correspondantes ne sont pas à 0.

Sinon ce serait d'éditer le script pour qu'il ajuste des variables plutôt que de donner directement des objets, comme ça tu pourrais directement les lire...

Je vois que le seul endroit où le script a l'air de donner des items est à la ligne 149 lorsqu'il fait

Portion de code : Tout sélectionner

1
2
3
4
5
 
for i in 0...@caught.size
  $game_party.gain_item($data_items[@caught[i]], 1)
end
 



Indirectement @caught contient des id d'items qui sont tirés de Fishes. L'enjeu de vouloir repurpose l'id pour que ce soit un id de variable c'est qu'il est aussi utilisé pour tirer l'info d'affichage... Je... pense que la solution la plus facile serait de tenir un tableau des noms de navires par varId?

Donc si mettons au lieu d'avoir

Portion de code : Tout sélectionner

1
2
3
 
# Fishes[ID] = ["Graphic",ItemID,SwitchID,Speed,Chances]
 



Ce serait

Portion de code : Tout sélectionner

1
2
3
4
 
# Fishes[ID] = ["Graphic",VarID,SwitchID,Speed,Chances]
# FishNames[VarID] = "Name"
 



Par exemple

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
Fishes[0] = ["Fish01", 186,      34,      2.0,    2]
Fishes[1] = ["Fish02", 187,      34,      1.4,    3]
Fishes[2] = ["Fish03", 188,      34,      1.0,    2]
Fishes[3] = ["Fish04", 189,      35,      3.0,    1]
Fishes[4] = ["Fish05", 190,      36,      3.0,    0.3]
 
FishNames = {}
FishNames[186] = "Navire"
FishNames[187] = "Croiseur"
FishNames[188] = "Porte-avion"
FishNames[189] = "Bombardier"
FishNames[190] = "Gold Porte-Avion"
 



Et ensuite il faut ajuster toutes les références à Fishes pour en prendre compte x)
Donc dans catch on pousserait donc un id de var plutôt qu'un id d'item dans @caught.

On peut alors mettre à jour le snippet du début pour passer par la variable plutôt, lignes 157-159:

Portion de code : Tout sélectionner

1
2
3
4
5
 
for i in 0...@caught.size
  $game_variables[@caught[i]] += 1
end
 



Et lorsque kinds est collecté dans finish pour déterminer ce qu'on affiche les noms, on utilisera FishNames, lignes 107-109:

Portion de code : Tout sélectionner

1
2
3
4
5
 
for i in 0...kinds.keys.size
  @finishWindow.contents.draw_text(0,40 + (18 * i),344,20,FishNames[kinds.keys[i]] + "    x   " +  kinds[kinds.keys[i]].to_s)
end
 



Et à priori maintenant ce sera la variables qui sont modifiées plutôt que les items - Dans l'exemple c'est les variables 186-190 qui auraient les gains de bateaux.

Ou le script avec les modifications (si je me plante pas...)

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
 
# =============================================================================
# Fishing MiniGame
# Version: 1.0
# Author: Omegas7
# Blog:  http://omegas7.blogspot.com    ||   http://omegadev.biz
# -----------------------------------------------------------------------------
# Features:
#    [1.0]
#       > Background & water surface images
#       > Water surface & fish have wave effect
#       > Aim with cursor image right & left
#       > Calculate strength with power meter & cursor
#       > Throw hook, if a fish is on landing area, catch.
#       > Multiple configurable sound effects
#       > Multiple configurable images
#       > Fish catching limit per session through variable (or infinite)
#       > Caught fish listing on end of session
#       > Configurable speed of power cursor & hook
#       > Link fish objects in script with items from database
#       > Receive items (fish)
#       > Fish become available through a game switch
#       > Edit chances of appearance for fish
#       > Accurate movement speed (decimal numbers) for fish
# -----------------------------------------------------------------------------
# NOTES
#       > When going to use the script, you need MINIMUN one fish available
#         (it's game switch ON)
# =============================================================================
 
module OmegaX
  module Fishing
    SessionFishLimitVariableID = 13 
    # Catchable fish per session variable ID.
    # If variable's value equals 0 or a negative number = infinite.
    
    PowerPointerSpeed = 9               # Higher = Faster
    HookThrownSpeed = 8                 # Higher = Faster
    
    ThrowSoundEffect = "Bow1"            # [Audio/SE/] folder
    CatchSoundEffect = "Item1"           # [Audio/SE/] folder
    MissSoundEffect = "Miss"             # [Audio/SE/] folder
    
    WaterSplashAnimationID = 188           # Database animation ID
    SpawningIntervals = [130,190,230]     # Possible frame intervals for spawning
    FishingBackground = "FishingBG"          # [Pictures] folder
    WaterSurface = ""            # [Pictures] folder
    FishingAim = "FishingAim"                # [Pictures] folder
    PowerBar = "FishingPowerBar"             # [Pictures] folder
    PowerPointer = "FishingPowerPointer"     # [Pictures] folder
    ThrowHook = "FishingHook"                # [Pictures] folder
    CatchMessage = "FishingCatchMessage"     # [Pictures] folder
    MissMessage = "FishingMissMessage"       # [Pictures] folder
    
    Fishes = []
    
    # Fishes[ID] = ["Graphic",varID,SwitchID,Speed,Chances]
    # Chances = The higher, the more probable
    Fishes[0] = ["Fish01",     186,      34,      2.0,    2]
    Fishes[1] = ["Fish02",     187,      34,      1.4,    3]
    Fishes[2] = ["Fish03",     188,      34,      1.0,    2]
    Fishes[3] = ["Fish04",     189,      35,      3.0,    1]
    Fishes[4] = ["Fish05",     190,      36,      3.0,    0.3]
 
    FishNames = {}
 
    # FishNames[varID] = "Name"
    FishNames[186] = "Navire"
    FishNames[187] = "Croiseur"
    FishNames[188] = "Porte-avion"
    FishNames[189] = "Bombardier"
    FishNames[190] = "Gold Porte-Avion"
  end
end
 
class OmegaFishing < Scene_Base
  include OmegaX::Fishing
  def initialize
    createArea
    createCursor
    createPossibleList
    @fishes = []
    @timer = [0,SpawningIntervals[rand(SpawningIntervals.size)]]
    @mode = "NOTHING"
    @pointerGoing = "RIGHT"
    @hookDistanceLeft = 0
    @animation = Sprite_Base.new
    @messages = []
    @limit = $game_variables[SessionFishLimitVariableID]
    executeSpawn
    executeSpawn
    @fix = false
    @caught = []
    @finished = false
  end
  def finish
    @finished = true
    @finishWindow = Window_Base.new(100,50,544 - 200,416 - 100)
    @finishWindow.contents.font.size = 18
    @finishWindow.contents.draw_text(0,0,344,20,"Navires détruits :")
    kinds = {}
    for i in 0...@caught.size
      if !kinds.keys.include?(@caught[i])
        kinds[@caught[i]] = 0
      end
      kinds[@caught[i]] += 1
    end
    for i in 0...kinds.keys.size
      @finishWindow.contents.draw_text(0,40 + (18 * i),344,20,FishNames[kinds.keys[i]] + "    x   " +  kinds[kinds.keys[i]].to_s)
    end
  end
  def update
    @animation.update if @animation.animation?
    @water.update
    updateFishMovement
    for i in 0...@messages.size
      @messages[i].y -= 2
      @messages[i].opacity -= 2
      if @messages[i].opacity <= 0
        @messages[i].dispose
        @messages[i] = nil
      end
    end
    @messages.compact!
    if !@finished
      @timer[0] += 1
      if @timer[0] >= @timer[1]
        @timer[0] = 0
        @timer[1] = SpawningIntervals[rand(SpawningIntervals.size)]
        executeSpawn
      end
      updateCursor if @mode == "NOTHING"
      updatePointer if @mode == "POWERING"
      updateHook if @mode == "THROWING"
      if Input.trigger?(Input::B)
        finish
      end
    else
      if Input.trigger?(Input::B) 
        @finishWindow.dispose
        for i in 0...@messages.size
          @messages[i].dispose
          @messages[i] = nil
        end
        @messages.compact!
        @water.dispose
        @animation.dispose
        for i in 0...@fishes.size
          @fishes[i][0].dispose
          @fishes[i] = nil
        end
        @fishes.compact!
        @hook.dispose if @hook != nil
        @powerBar.dispose if @powerBar != nil
        @powerPointer.dispose if @powerPointer != nil
        @cursor.dispose
        @bg.dispose
        for i in 0...@caught.size
          $game_variables[@caught[i]] += 1
        end
        $game_switches[309] = true
        $scene = Scene_Map.new
      end
    end
  end
  def catch(obtained,x,y,fish = 0,id = 0)
    @messages.push(Sprite_Base.new)
    if obtained
      Audio.se_play("Audio/SE/" + CatchSoundEffect)
      @caught.push(id)
      @messages[@messages.size - 1].bitmap = Cache.picture(CatchMessage)
      @fishes[fish][0].dispose
      @fishes[fish] = nil
    else
      Audio.se_play("Audio/SE/" + MissSoundEffect)
      @messages[@messages.size - 1].bitmap = Cache.picture(MissMessage)
    end
    @fishes.compact!
    @messages[@messages.size - 1].x = x
    @messages[@messages.size - 1].y = y
    @messages[@messages.size - 1].z = 20
    @mode = "NOTHING"
    @hook.dispose
    @hook = nil
    @powerPointer.dispose
    @powerPointer = nil
    @powerBar.dispose
    @powerBar = nil
    if @limit > 0   # Limited catchable fish
      if @caught.size >= @limit
        finish
      end
    end
  end
  def updateFishMovement
    for i in 0...@fishes.size
      @fishes[i][2] += Fishes[@fishes[i][1]][3]
      while @fishes[i][2] >= 1.0
        @fishes[i][2] -= 1.0
        @fishes[i][0].x += 1
      end
      @fishes[i][0].update
    end
  end
  def updateCursor
    if Input.press?(Input::RIGHT)
      @cursor.x += 3 if @cursor.x + @cursor.width < 544
    end
    if Input.press?(Input::LEFT)
      @cursor.x -= 3 if @cursor.x > 0
    end
    if Input.trigger?(Input::C) && @mode == "NOTHING"
      @mode = "POWERING"
      @powerBar = Sprite_Base.new
      @powerBar.bitmap = Cache.picture(PowerBar)
      @powerBar.z = 10
      @powerPointer = Sprite_Base.new
      @powerPointer.bitmap = Cache.picture(PowerPointer)
      @powerPointer.z = 11
      @fix = false
      return
    end
  end
  def updatePointer
    case @pointerGoing
    when "RIGHT"
      @powerPointer.x += PowerPointerSpeed
      if @powerPointer.x + @powerPointer.width >= 544
        @pointerGoing = "LEFT"
      end
    when "LEFT"
      @powerPointer.x -= PowerPointerSpeed
      if @powerPointer.x < 0
        @pointerGoing = "RIGHT"
      end
    end
    if Input.trigger?(Input::C) && @mode == "POWERING" && @fix
      Audio.se_play("Audio/SE/" + ThrowSoundEffect)
      @mode = "THROWING"
      @hook = Sprite_Base.new
      @hook.bitmap = Cache.picture(ThrowHook)
      @hook.z = 12
      @hook.x = (@cursor.x + (@cursor.width/2)) - @hook.width/2
      @hook.y = @cursor.y
      @hookDistanceLeft = (416.0 * (@powerPointer.x/544.0)).abs
      return
    end
    @fix = true
  end
  def updateHook
    if @hookDistanceLeft > 0
      @hook.y -= HookThrownSpeed
      @hookDistanceLeft -= HookThrownSpeed
    else
      playSplash(@hook.x,@hook.y)
      hookRect = Rect.new(@hook.x,@hook.y,@hook.width,@hook.height)
      for i in 0...@fishes.size
        fishRect = Rect.new(@fishes[i][0].x,@fishes[i][0].y,@fishes[i][0].width,@fishes[i][0].height)
        if hookRect.intersects?(fishRect)
          catch(true,@hook.x,@hook.y,i,Fishes[@fishes[i][1]][1])
          return
        end
      end
      catch(false,@hook.x,@hook.y)
    end
  end
  def playSplash(x,y)
    @animation.x = x
    @animation.y = y
    @animation.start_animation($data_animations[WaterSplashAnimationID])
  end
  def executeSpawn
    @fishes.push([Sprite_Base.new,@possibleFishes[rand(@possibleFishes.size)],0.0])
    id = @fishes.size - 1
    @fishes[id][0].bitmap = Cache.picture(Fishes[@fishes[id][1]][0])
    @fishes[id][0].z = 1
    @fishes[id][0].x = -(@fishes[id][0].width)
    @fishes[id][0].y = rand(416) - 100
    @fishes[id][0].wave_amp = 8
    @fishes[id][0].wave_length = 300
    @fishes[id][0].wave_speed = 200
  end
  def createArea
    @bg = Sprite_Base.new
    @bg.bitmap = Cache.picture(FishingBackground)
    @bg.z = 0
    @water = Sprite_Base.new
    @water.bitmap = Cache.picture(WaterSurface)
    @water.z = 2
    @water.x = (544/2) - (@water.width/2)
    @water.wave_amp = 8
    @water.wave_length = 240
    @water.wave_speed = 120
  end
  def createCursor
    @cursor = Sprite_Base.new
    @cursor.bitmap = Cache.picture(FishingAim)
    @cursor.z = 10
    @cursor.x = (544/2) - (@cursor.width/2)
    @cursor.y = 416 - (@cursor.height/2)
  end
  def createPossibleList
    @possibleFishes = []
    for i in 0...Fishes.size
      if $game_switches[Fishes[i][2]]
        chances = Fishes[i][4]
        while chances > 0
          @possibleFishes.push(i)
          chances -= 1
        end
      end
    end
  end
end
 
 
# Lol I still have this snippet BigEd did once :P
class Rect
  def intersects?( rect )
    return ((((rect.x < (self.x + self.width)) && (self.x < (rect.x + rect.width))) && (rect.y < (self.y + self.height))) && (self.y < (rect.y + rect.height)))
  end
end
 



Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 20/01/2023 à 16:31:25. (8 messages postés)

Ouais pour les dialgoues c'est pas tout à fait réglé encore... Je me demande si la qualité très variable des enregistrements y joue pas pour quelque chose, entres autres le post-processing que je fais pour normaliser semble ne pas super bien fonctionner.

C'est absolument requis les doublages dans ma tête! :P
Mais je ne pense pas que le jeu sera tant long que ça, probablement une durée de 6~8 heures au final

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 20/01/2023 à 06:37:37. (8 messages postés)

Rebonjour!

Donc je reviens avec de petites mises à jour!

On a paufiné un peu plus le 2e chapitre - Le combat avec le Windô a maintenant droit à sa propre musique, et on a enregistré les voix pour toutes les lignes.



Je commence tranquillement le troisième chapitre, et j'en profite pour faire quelques ajouts à l'engin qui me semblent sympathiques!

Si ça peut vous intéresser, je mets tout dans un script pêle-mêle.

Quand j'ai commencé à enregistrer les voix originellement j'ai vite réalisé que dans le mix c'était un peu trop difficile à comprendre les dialogues - on entendait typiquement très mal les voix sans descendre la musique à mort, ou sans booster les effets sonores de manière désagréable.

J'avais d'abord introduit que ça modélise le volume des voix séparément du reste du volume en profitant que je préfixe tout les fichiers de voix avec "_", mais c'était quand même pas idéal parce que le niveau confortable de la musique durant les dialogues était trop bas à mon goût par rapport au niveau confortable durant le gameplay.
J'ai donc rajouté une détection dans le playback des SEs pour compter si y'a des voix qui jouent, et alors le volume de la musique quand les dialogues commencent et le remonter quand plus personne ne parle - ça fait un genre de "dip" à 25% du volume et ça a pas mal aidé.

Il se trouve en fait que le pipeline audio de RPG Maker MV est en webaudio, et récemment ça m'a fait cliquer que ça implique que je peux rajouter des effets sonores en post!

Donc là je me suis rajouté un meta tag sur les cartes <bgmFilter> qui supporte deux valeurs pour l'instant:

* <bgmFilter:insideAway> qui applique un gros lowpass sur la musique pour sonner comme si on était dans la pièce d'à côté
* <bgmFilter:farAway> qui applique un peu de lowpass et pas mal de reverb et qui donne donc l'impression d'être très loin

Un exemple de ce que ça donne:



Donc dans un override de la lecture des fichiers, sur la prop "bgm" j'ajoute le filtre désigné dans le meta -

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
override(DataManager,
    function onLoad(onLoad, object) {
        onLoad.call(DataManager, object);
        switch (object) {
            case $dataMap:
                $dataMap.bgm.filters = ($dataMap.meta.bgmFilter || "").split(",").filter(i => i);
                break;
        }
    });



Puis elle est récupérée lorsque AudioManager mets à jour les options du buffer (qui est le machin qui s'occupe du playback et qui contient le pipeline webaudio) -

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
override(AudioManager,
    function updateBufferParameters(updateBufferParameters, buffer, configVolume, audio) {
        updateBufferParameters.call(this, buffer, configVolume, audio);
        if (buffer && audio) {
            buffer.filters = audio.filters || null;
        }
    },
    function updateCurrentBgm(updateCurrentBgm, bgm, pos) {
        updateCurrentBgm.call(this, bgm, pos);
        this._currentBgm.filters = bgm.filters;
    },
    function saveBgm(saveBgm) {
        const bgm = saveBgm.call(this);
        this._currentBgm && (bgm.filters = this._currentBgm.filters);
        return bgm;
    });



Et donc dans le buffer WebAudio je modélise si c'est un bgm, et dans le traitement des noeuds je rajoute le lowpass, le reverb, et des gains pour contrôller le wet/dry -

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
override(WebAudio,
    function _createContext(_createContext) {
        _createContext.call(this);
 
        impulseResponse = ( duration, decay, reverse ) => {
            let sampleRate = this._context.sampleRate;
            let length = sampleRate * duration;
            let impulse = this._context.createBuffer(2, length, sampleRate);
            let impulseL = impulse.getChannelData(0);
            let impulseR = impulse.getChannelData(1);
 
            if (!decay)
                decay = 2.0;
            for (let i = 0; i < length; i++){
                let n = reverse ? length - i : i;
                impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
                impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
            }
            return impulse;
        };
        this._convolverBuffer = impulseResponse(1, 1, false);
 
        this.filters = {
            insideAway: {
                _lowpass: { wet: 1 },
                _reverb: { wet: 0, dry: 1 }
            },
            farAway: {
                _lowpass: { wet: 0.5 },
                _reverb: { wet: 1, dry: 0.5 }
            },
            default: {
                _lowpass: { wet: 0 },
                _reverb: { wet: 0, dry: 1 }
            }
        }
    });
 
override(WebAudio.prototype,
    function _startPlaying(_startPlaying, loop, offset) {
        this._isBgm = !!url.match(bgmRegexp);
        return _startPlaying.call(this, loop, offset);
    },
    function _createNodes(_createNodes) {
        if (!this._isBgm) {
            return _createNodes.call(this);
        }
 
        _createNodes.call(this);
 
        const context = WebAudio._context;
 
        this._lowpassNode = context.createBiquadFilter();
        this._lowpassNode.type = "lowpass";
        this._lowpassDry = context.createGain();
        this._lowpassWet = context.createGain();
        this._reverbNode = context.createConvolver();
        this._reverbNode.buffer = WebAudio._convolverBuffer;
        this._reverbDry = context.createGain();
        this._reverbWet = context.createGain();
 
        this._updateFilters(false);
    },
    function _connectNodes(_connectNodes) {
        if (!this._isBgm) {
            return _connectNodes.call(this);
        }
 
        this._gainNode.connect(this._pannerNode);
        this._pannerNode.connect(this._reverbDry);
        this._pannerNode.connect(this._reverbNode);
        this._reverbNode.connect(this._reverbWet);
        this._reverbDry.connect(this._lowpassDry);
        this._reverbDry.connect(this._lowpassNode);
        this._reverbWet.connect(this._lowpassDry);
        this._reverbWet.connect(this._lowpassNode);
        this._lowpassNode.connect(this._lowpassWet);
        this._lowpassDry.connect(WebAudio._masterGainNode);
        this._lowpassWet.connect(WebAudio._masterGainNode);
    },
    function _removeNodes(_removeNodes) {
        if (!this._isBgm) {
            return _removeNodes.call(this);
        }
 
        _removeNodes.call(this);
 
        this._lowpassNode = null;
        this._lowpassDry = null;
        this._lowpassWet = null;
        this._reverbNode = null;
        this._reverbDry = null;
        this._reverbWet = null;
    },
    function _updateFilters(ramp) {
        const context = WebAudio._context;
        const rampTime = ramp ? 1 : 0;
        if (this._lowpassDry) {
            this._lowpassDry.gain.linearRampToValueAtTime(1 - this._lowpass.wet, context.currentTime + rampTime);
            this._lowpassWet.gain.linearRampToValueAtTime(this._lowpass.wet, context.currentTime + rampTime);
        }
        if (this._reverbDry) {
            this._reverbDry.gain.linearRampToValueAtTime(this._reverb.dry, context.currentTime + rampTime);
            this._reverbWet.gain.linearRampToValueAtTime(this._reverb.wet, context.currentTime + rampTime);
        }
    });
 
Object.defineProperty(WebAudio.prototype, "filters", {
    get() {
        return this._filters;
    },
    set(filters) {
        this._filters = this._filters;
        const filter = filters && filters[0] || null;
        const toApply = WebAudio.filters[filter] || WebAudio.filters.default;
        for (const key in toApply) {
            this[key] = toApply[key];
        }
        this._updateFilters(true);
    }
});



Sur le coup j'étais pas trop sûr de l'API exact de noeuds webaudio, mais c'est assez direct en fait - quand on fait "X.connect(Y)" ça dit que l'output de X va aller dans Y.

Donc si on cherche à peu près le diagramme

Portion de code : Tout sélectionner

1
source -> reverb -> lowpass -> output



En noeuds ça donne

Portion de code : Tout sélectionner

1
2
source -> reverb -> reverbWet (gain) v> lowpass -> lowpassWet (gain) > output
       -> reverbDry (gain)           ^> lowpassDry (gain)            ^



Le wet va dans les deux noeuds du prochain effet, et même chose pour le dry - Il faut juste faire attention parce que si wet et dry sont au max ça "double" le volume.

Sinon un autre ajout que j'ai fait récemment est de remplacer les fade-to-black des transfers pour des crossfade. Je taponnais un peu dans RPG Maker XP cette semaine et ça m'a rappeler qu'à l'époque c'était des crossfade plutôt, et personnellement je trouve ça plus agréable.
On peut le voir un peu dans le deux vidéo plus haut - Le seul truc qui m'agace c'est que des fois ça stutter, mais bon en général la performance de RPG Maker Mv est plutôt conceptuelle.

Au final c'était pas trop difficile à faire - le jeu supporte déjà de prendre des clips pour le fond du menu, et le fade-to-black c'est un sprite qui est mis par dessus la scène, alors j'ai bidouillé que les fade de transfers utilise un sprite alternatif qui est un clip de la scène -

Portion de code : Tout sélectionner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Transitiooooons
(function () {
    const CROSSFADE = 0;
    let crossfadeSprite;
    let crossfadeBitmap;
    let crossfadeRenderTexture;
    override(Graphics,
        function initialize(initialize, width, height, type) {
            initialize.call(this, width, height, type);
 
            crossfadeBitmap = new Bitmap(width, height);
            crossfadeRenderTexture = PIXI.RenderTexture.create(width, height);
            crossfadeSprite = new Sprite();
            crossfadeSprite.bitmap = crossfadeBitmap;
        })
    override(Scene_Map.prototype,
        function createCrossfadeSprite(_, fadein) {
            if (this._fadeSprite !== crossfadeSprite) {
                if (this._fadeSprite) {
                    this.removeChild(this._fadeSprite);
                }
                this._fadeSprite = crossfadeSprite;
                this.addChild(this._fadeSprite);
            }
            if (!fadein) {
                Graphics._renderer.render(this._spriteset, crossfadeRenderTexture);
                this.worldTransform.identity();
                let canvas = Graphics._renderer.extract.canvas(crossfadeRenderTexture);
                crossfadeBitmap._context.drawImage(canvas, 0, 0);
                crossfadeBitmap._setDirty();
            }
        },
        function createFadeSprite(createFadeSprite, white) {
            if (this._fadeSprite === crossfadeSprite) {
                this.removeChild(crossfadeSprite);
                delete this._fadeSprite;
            }
            createFadeSprite.call(this);
        },
        function startFadeIn(startFadeIn, duration, type) {
            if (type === CROSSFADE) {
                this.createCrossfadeSprite(true);
                this._fadeSign = 1;
                this._fadeDuration = duration || 30;
                this._fadeSprite.opacity = 255;
            } else {
                startFadeIn.call(this, duration, type);
            }
        },
        function startFadeOut(startFadeOut, duration, type) {
            if (type === CROSSFADE) {
                this.createCrossfadeSprite(false);
                this._fadeDuration = -1;
                this._fadeSprite.opacity = 0;
            } else {
                startFadeOut.call(this, duration, type);
            }
        },
        function fadeInForTransfer() {
            this.startFadeIn(this.fadeSpeed(), CROSSFADE);
        },
        function fadeOutForTransfer() {
            this.startFadeOut(-1, CROSSFADE);
        });
})();



J'ai pas directement utilisé le Bitmap.snap de l'engin par contre parce que je voulais éviter d'avoir à instancier un nouveau buffer à chaque fois... c'est le genre de chose qui cause du stutter et que l'engin fait incroyablement pas attention à éviter x)

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 07/01/2023 à 18:08:59. (8 messages postés)

Si tu connais un Franco-Québécois avec trop de temps qui est prêt à faire les adaptations et à redoubler, je suis tout ouïe :P

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 06/01/2023 à 21:29:36. (8 messages postés)

Je confirme Troma que "cossin" c'est une expression québécoise pour une chose :P

Dans ce cas-ci je planifie éventuellement contextualiser un peu pourquoi y'a un vieux qui cherche un cossin, mais il va falloir attendre un peu pour ça!

Sinon, le balancing ça m'a donné du fil à retordre jusqu'ici - j'essaye de viser suffisament dur que tu peux pas juste spammer des attaques, mais pas tellement difficile que tout doit être optimisé aux petits oignons. Le coup de la dague au début par contre... Ça doit bien faire 4-5 personnes que je vois jouer qu'y l'oublient x). J'espérais que le petit bout de dialogue qu'y rappelle + le fait que tu peux fuir les rats suffise, mais à date c'est un échec.

Citation:

En tout cas, il a du y avoir du travail parce qu’enregistrer tout les textes et les caler dans chaque boite de dialogue, ca a du être bien chiant et long a faire , j'avais déja fait ca aussi et j'ai vite abandonné l'idée après trois maps (et puis parceque mes .wma que je ne savait pas convertir pour rm2000 a l'époque faisaient pesés un âne mort a mon projet).



Ouch ce devait être douloureux x). Ça m'a plutôt aidé de savoir programmer à date en fait - j'ai un fichier où j'inscris les dialogues à enregistrer avec le titre du fichier, et ensuite j'ai un script qui génère des fichiers audio placeholder, comme ça au fur et à mesure que j'écris les dialogues je peux tout mettre en place tout de suite sans avoir à enregistrer immédiatement ou revenir à la fin pour remodifier les choses.

Merci d'avoir essayé le jeu :)

Posté dans Forum - [RMMV] Le Bon Matin, la Laitue, et la Tomate

dagothig - posté le 03/01/2023 à 19:59:46. (8 messages postés)

Par une journée enneigée, vous vous réveillez avec un trou béant dans votre Être.
Un besoin que seul une chose peut combler.
Une faim cosmique vous tenaille, et vous n'avez pas d'autre choix que de partir en quête du fameux Bon-Matin-Laitue-Tomate...

image

Dans cette Grande Aventure :tm:, vous ferez la découverte d'un Monde, et de personnages écrits alors qu'une Quête se déroulera!

image
image

Circa 2006 je trainais pas mal sur les forums de rpg creative (rip), et l'an passé lorsque j'ai vu que rpg maker mv avait un gros rabais, je me suis dis que ça serait drôle de me relancer sur rpg maker!

Ça fait donc un presque un an que je travaille sur un petit rpg absolument stupide, mais entièrement doublé avec l'aide de quelques amis, et avec des musiques custom (gracieuseté de mon bon ami adelrune). Ça a pas mal été écrit pour moi-même alors vous allez devoir m'excuser les blagues plutôt insulaires, mais j'ai espoir que vous allez tout de même y trouver un petit chuckle ou deux.

J'ai terminé à peu près ~25% du jeu je crois, ou quelque chose comme les deux premiers chapitres. C'est un jeu rpg maker quand même très typique, autre que j'ai coupé le levelling et les combats aléatoires, parce que je trouve ça superflu.

C'est disponible sur mon site, ou sur github.
D'ailleurs, si ça intéresse quelqu'un, les sources sont disponibles.

Merci!

Aller à la page: 1

Haut de page

Merci de ne pas reproduire le contenu de ce site sans autorisation.
Contacter l'équipe - Mentions légales

Plan du site

Communauté: Accueil | Forum | Chat | Commentaires | News | Flash-news | Screen de la semaine | Sorties | Tests | Gaming-Live | Interviews | Galerie | OST | Blogs | Recherche
Apprendre: Visite guidée | RPG Maker 95 | RPG Maker 2003 | RPG Maker XP | RPG Maker VX | RPG Maker MV | Tutoriels | Guides | Making-of
Télécharger: Programmes | Scripts/Plugins | Ressources graphiques / sonores | Packs de ressources | Midis | Eléments séparés | Sprites
Jeux: Au hasard | Notre sélection | Sélection des membres | Tous les jeux | Jeux complets | Le cimetière | RPG Maker 95 | RPG Maker 2000 | RPG Maker 2003 | RPG Maker XP | RPG Maker VX | RPG Maker VX Ace | RPG Maker MV | Autres | Proposer
Ressources RPG Maker 2000/2003: Chipsets | Charsets | Panoramas | Backdrops | Facesets | Battle anims | Battle charsets | Monstres | Systems | Templates
Ressources RPG Maker XP: Tilesets | Autotiles | Characters | Battlers | Window skins | Icônes | Transitions | Fogs | Templates
Ressources RPG Maker VX: Tilesets | Charsets | Facesets | Systèmes
Ressources RPG Maker MV: Tilesets | Characters | Faces | Systèmes | Title | Battlebacks | Animations | SV/Ennemis
Archives: Palmarès | L'Annuaire | Livre d'or | Le Wiki | Divers