Posté le: 12/08/10 06:57 Sujet du message: Les sons: exemple des projectiles
I/ Introduction
Situation : un membre du forum (Nuage) n’arrive pas à entendre le son qu’il aimerait pour une unité (tourelle d’un Tower Defence) lorsque celle-ci attaque une autre unité (un monstre).
Il veut que le son d’attaque de l’unité Marine (qui est le même que celui de l’attaque 1 de la Machine Volante) soit joué, pour produire ainsi le bruit d’une mitrailleuse.
Objectif de ce tutoriel : présenter les différentes méthodes pour faire écouter aux joueurs un son à partir de l’attaque d’une unité.
Je suivrai donc l’exemple du cas de Nuage pour présenter ces méthodes, avec chacune leurs avantages et inconvénients.
Il n’y a pas de pré-requis pour l’ensemble du tutorial, mais ils seront indiqués selon les différentes méthodes.
Note : encore une fois, l’exemple montre comment entendre un bruit de mitrailleuse au cours de l’attaque d’un monstre par une tour. Mais les méthodes présentées fonctionnent pour tout type de lecture de son 3D.
Note 2 : pour éviter que le tutoriel parte dans tous les sens et pour des raisons évidentes de simplification, on va partir sur un exemple encore plus précis : la tour utilisera le modèle de Cercle de Puissance et le projectile celui de l’attaque 1 de la Machine Volante.
La tour
Unités/Neutre passif/Campagne/Bâtiments/Cercle de puissance. ID entier associé : ‘ncop’
Chemin dans l’archive MPQ de Warcraft 3 :
Buildings\Other\CircleOfPower\CircleOfPower.mdx
Le projectile:
Unités-Projectiles /Machine Volante (le premier des deux proposés)
Chemin dans l’archive MPQ de Warcraft 3 :
Abilities\Weapons\GyroCopter\GyroCopterImpact.mdx
Vous pouvez faire le test, si vous utilisez une tour avec ce modèle en lui donnant comme attaque le projectile ci-dessus, vous aurez bien les effets visuels, mais aucun son.
Voyons maintenant comment entendre notre son, qui, manifestement, n’existe pas ni sur la tour, ni sur le projectile.
Lire le tutoriel de Rhadamante sur Warcraft 3 Model Editor
On va commencer par ouvrir Warcraft 3 Model Editor. J’espère que vous savez faire.
On ne perd pas de temps : le but est de retrouver le modèle du cercle de puissance.
Allez donc cliquer sur Window/MPQ Browser.
Ce menu permet de consulter l’ensemble des fichiers présents dans l’archive MPQ de Warcraft 3.
Il faut sélectionner le bon MPQ. Je vous aide, on va trouver le cercle de puissance dans War3 (le MPQ de Reign Of Chaos).
Ensuite, il faut chercher notre modèle.
Le notre se situe ici : Buildings\Other\CircleOfPower\CircleOfPower.mdx
Une fois que vous avez ouvert votre fichier, vous pouvez déjà l’enregistrer quelque part en faisant File/Save As comme sur n’importe quel programme.
Histoire de passer aux choses sérieuses:
Dans un Modèle de Warcraft 3, on trouve un certain nombre de propriétés appellées « Nodes ». Un Notes, ça peut être un ensemble de points du modèles (Bones), un émetteur de Particules, un Son etc…
On va donc ouvrir le module de Warcrat 3 Model Editor appelé Node Manager
Vous devriez voir la meme fenêtre que sur ma capture d’écran.
Il y a une série d’objets appelés Plane avec un symbole d’os. Ce sont des Bones (os en Français), en général on s’en sert pour grouper des séries de points (ceux-là formant le design du modèle).
Omni01 est une lumière. Ca va créer un effet d’éclairage.
Dummy01 avec un point d’exclamation, c’est un Helper. On va le considérer comme un Bone : on s’en fiche toujours.
Sprite Rallye Point Ref, avec un trombone, c’est un attachement. Si vous lancez un sort avec comme infographie-cible l’attachement Sprite Rallye Point Ref, l’effet du sort sera créé sur ce point du modèle.
Bref, toujours pas pour nous.
La boîte appelée Collision, c’est pareil, elle ne nous concerne pas.
Mais alors, où est le son de l’unité ?
La réponse : elle n’en n’a pas. On va donc le créer.
Avantage
Le son est intégré dans le modèle, donc il se joue sans problème pour toutes les unités de la carte, en temps réel.
Inconvénient
Si vous ne savez pas éditer les modèles, vous allez devoir apprendre à créer des Event Objects via Warcraft 3 Model Editor par exemple. En plus cela oblige à importer les modèles, donc à alourdir la carte.
Le son est intégré dans le modèle, donc il se joue sans problème pour toutes les unités de la carte, en temps réel.
Contrairement à l'importation d'unités complètes, les modèles de projectiles sont bien plus légers.
Inconvénient
Si vous ne savez pas éditer les modèles, vous allez devoir apprendre à créer des Event Objects via Warcraft 3 Model Editor par exemple, pour chaque projectile que vous voulez utiliser.
IV/ Remplacer le son d’un modèle avec son intégré
3-Vous utilisez un projectile qui utilise déjà un son, et vous remplacez le son utilisé par ce projectile par celui de votre choix.
Vous devez donc importer un son et lui donner le chemin complet de celui utilisé par le projectile en chemin personnalisé via l'éditeur d'importation.
Avantage
N'oblige pas à modifier des modèles et à les importer.
Inconvénient
Les poids des sons importés sont élevés en général, pour pas grand chose de plus au final.
Cela oblige à trouver un projectile (n'importe quel modèle du jeu) pour lequel vous vous moquez du son, donc que vous n'allez pas utiliser plus tard: par exemple si vous choisissez d'utiliser le modèle de Feu Sacré, à chaque fois que ce modèle servira, il aura votre son de sulfateuse.
En plus vous allez devoir trouver le chemin complet du son à remplacer quand vous importerez le votre.
V/ Jouer le son par déclencheur en Gui
4-Vous jouez un son par déclencheur en détectant l'attaque de la tour en question. A priori vous pouver coder en Gui donc la seule solution offerte par le Gui c'est :
Secret:
Gui:
Trigger:
Untitled Trigger 001 Events Unit - A unit Is attacked
Conditions
(Unit-type of (Attacking unit)) Equal to Goblin Sulfate-Machine
Actions
Sound - Play Sulfateuse <gen> at 100.00% volume, attached to (Attacking unit)
Avantage
Méthode la plus "légère": aucune modification à faire sur les modèles et les projectiles, juste un petit déclencheur.
Inconvénient
Warcraft 3 gère mal les sons. Du coup, en utilisant ces fonctions, il ne peut y avoir qu'un seul son 3D joué à la fois en Gui. Si vous avez 30 tourelles qui tirent toutes les 0.05 secondes, une grosse partie des sons ne sera pas jouée.
En plus, le son ne sera joué que lorsque la cible de ta tour sera attaquée. Si vous utilisez des projectiles lents sur des distances importantes, ça va faire bizarre d'entendre le son du canon en train de tirer alors que le projectile sera seulement en train d'arriver sur la cible.
VI/ Jouer le son par déclencheur en Jass
5-Il faut apprendre le Jass et le Vjass et vous servir de la library SoundUtils. Nécessite le JassNewGenPack, la library Stack et la library TimerUtils en support.
Pour l'installation d'une library: vous créez un déclencheur. Vous le sélectionnez, faites Edition/Convertir en texte personnalisé. Vous supprimez tout le contenu du déclencheur convertit. Vous copiez le code de la library.
Vous copiez SoundUtils
Secret:
Jass:
library SoundUtils requires Stack, TimerUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* Sounds are a very picky datatype in WC3. They have many quirks that one must
//* account for in order to use them, and simply using the internal WE Sound
//* Editor isn't enough because the sounds it makes can't be played multiple
//* times at once. 3-D sounds are also very tricky because there are different
//* WC3 sound options that a user can have activated where certain sounds will
//* or will not work. This library attempts to streamline the handling of sounds
//* so that it is less likely to confuse you or cause problems.
//*
//* The .mp3 format can be used for 3-D sounds, but there is one problem that
//* must be noted. If your computer supports the "Dolby Surround" sound option
//* in WC3 and you have it selected, then .mp3 files will work for 3-D sounds.
//* If you don't, however, they may not work depending on what you do have
//* selected and what is available for your computer. The .wav format works on
//* all possible settings, making them excellent for general use. This library
//* can interface with sounds of either type.
//*
//* Known issues with sounds that this library resolves:
//* - A given sound variable can only be played once at a time. In order to
//* play a sound type multiple times at once, you need multiple variables.
//* - A sound cannot be played at the same instant that it is created.
//*
//* The DefineSound function defines a sound type based on some basic parameters
//* the user provides. DefineSoundEx is available if the user wants control over
//* all possible parameters, though they won't have an impact most of the time.
//* The duration parameter for DefineSound and DefineSoundEx is in milliseconds,
//* which is consistent with Blizzard's natives. To get the duration of a given
//* sound, open up the WE's Sound Editor, navigate to your sound, and select
//* "Add as Sound." In doing so, it will show its duration in seconds. Multiply
//* that number by 1000 and use it as the duration argument.
//*
//* This library returns a sound variable with RunSound that you can change the
//* settings of using the standard JASS sound API. The library assigns default
//* values to the parameters for 2-D and 3-D sounds, that way they will run
//* without any further help.
//*
//* The library automatically allocates, runs, and recycles a sound when you
//* call RunSound. This library will not automatically recycle looping sounds,
//* so you will need to call ReleaseSound on the looping sound when you want it
//* to end.
//*
//******************************************************************************
//*
//* > function DefineSound takes string fileName, integer duration, ...
//* boolean looping, boolean is3D returns integer
//*
//* This function defines a sound type with a short list of parameters. The
//* returned integer serves as a SOUND_TYPE for running this type of sound at
//* any other point in a map.
//*
//* > function DefineSoundEx takes string fileName, integer duration, ...
//* boolean looping, boolean is3D, boolean stopwhenoutofrange, ...
//* integer fadeInRate, integer fadeOutRate, string eaxSetting ...
//* returns integer
//*
//* This function serves an identical purpose to DefineSound, but gives the user
//* full control over the entire list of parameters. Similar to DefineSound, the
//* returned integer serves as a SOUND_TYPE for running this type of sound.
//*
//* > function RunSound takes integer soundRef returns sound
//*
//* This function runs a sound with the parameters held within the soundRef
//* integer argument. The soundRef argument is the returned value of DefineSound
//* or DefineSoundEx.
//*
//* > function RunSoundOnUnit takes integer soundRef, unit whichUnit returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type on a
//* specified unit.
//*
//* > function RunSoundAtPoint takes integer soundRef, real x, real y, real z returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type at a
//* specified point in 3D space.
//*
//* > function RunSoundForPlayer takes integer soundRef, player p returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type only
//* for the specified player.
//*
//* > function ReleaseSound takes sound s returns boolean
//*
//* This function need only be called on looping sounds. If a sound is not
//* looping, it will be released and recycled on its own. This function should
//* be used on looping sounds when you want them to end.
//*
//* Example usage:
//* set SOUND_TYPE = DefineSound("Sound\\Path.wav", 300, false, true)
//* call RunSound(SOUND_TYPE)
//* call RunSoundOnUnit(SOUND_TYPE, SomeUnit)
//* call RunSoundAtPoint(SOUND_TYPE, x, y, z)
//* call RunSoundForPlayer(SOUND_TYPE, Player(5))
//* call ReleaseSound(SomeLoopingSound)
//*
globals
private hashtable ht = InitHashtable() //Attach sound types to sounds
private hashtable st = InitHashtable() //Sound hashtable
private hashtable rt = InitHashtable() //Attach soundrecyclers to sounds
private hashtable kt = InitHashtable() //Attach StopSound data
endglobals
//Struct for each sound type
private struct soundhelper
//Stack associated to each struct
Stack sta
static method create takes string fileName, integer duration, boolean looping, boolean is3D, boolean stopwhenoutofrange, integer fadeInRate, integer fadeOutRate, string eaxSetting returns soundhelper
local soundhelper sh = soundhelper.allocate()
//Load the parameters so the sound can be created later as necessary
set sh.fileName = fileName
set sh.duration = duration
set sh.looping = looping
set sh.is3D = is3D
set sh.stopwhenoutofrange = stopwhenoutofrange
set sh.fadeInRate = fadeInRate
set sh.fadeOutRate = fadeOutRate
set sh.eaxSetting = eaxSetting
//Create the stack for the struct
set sh.sta = Stack.create()
return sh
endmethod
endstruct
//Struct for holding data for the sound recycling
private struct soundrecycler
timer t = null
sound s = null
integer sh = 0
boolean stopped = false //Only gets used if StopSound is called on a new sound
static method create takes sound whichSound, integer soundRef returns soundrecycler
local soundrecycler sr = soundrecycler.allocate()
set sr.t = NewTimer()
set sr.s = whichSound
set sr.sh = soundRef
call SetTimerData(sr.t, integer(sr))
//Hook the value to the soundRef and whichSound
call SaveInteger(rt, soundRef, GetHandleId(whichSound), integer(sr))
return sr
endmethod
private method onDestroy takes nothing returns nothing
call RemoveSavedInteger(rt, .sh, GetHandleId(.s))
call ReleaseTimer(.t)
endmethod
endstruct
private function HookStopSound takes sound soundHandle, boolean killWhenDone, boolean fadeOut returns nothing
local integer id = GetHandleId(soundHandle)
local integer soundRef = 0
local soundrecycler sr = 0
if HaveSavedInteger(ht, 0, id) then //Sound is from stacks
set soundRef = LoadInteger(ht, 0, id)
if HaveSavedInteger(rt, soundRef, id) then //Sound has a recycler
set sr = soundrecycler(LoadInteger(rt, soundRef, id))
set sr.stopped = true
endif
if killWhenDone then
debug call BJDebugMsg(SCOPE_PREFIX+"Warning: (StopSound) Destroying a sound in the stack")
endif
endif
endfunction
hook StopSound HookStopSound
private function HookKillSoundWhenDone takes sound soundHandle returns nothing
if HaveSavedInteger(ht, 0, GetHandleId(soundHandle)) then
call BJDebugMsg(SCOPE_PREFIX+"Warning: (KillSoundWhenDone) Destroying a sound in the stack")
endif
endfunction
function ReleaseSound takes sound s returns boolean
local integer id = GetHandleId(s)
local integer soundRef = 0
local soundhelper sh = 0
local soundrecycler sr = 0
if s == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot recycle a null sound")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot recycle a sound not allocated by RunSound")
return false
endif
set soundRef = LoadInteger(ht, 0, id)
set sh = soundhelper(soundRef)
call StopSound(s, false, true) //Stop the sound
call sh.sta.push(id) //Return it to the stack
call SaveSoundHandle(st, soundRef, id, s) //Save it to hashtable
if not sh.looping then
//soundrecycler only exists for non-looping sounds
set sr = soundrecycler(LoadInteger(rt, soundRef, id))
call sr.destroy() //Destroy recycler helper
endif
return true
endfunction
private function Recycle takes nothing returns nothing
local soundrecycler sr = soundrecycler(GetTimerData(GetExpiredTimer()))
local soundhelper sh = soundhelper(sr.sh)
local integer id = GetHandleId(sr.s)
call StopSound(sr.s, false, true) //Stop the sound
call sh.sta.push(id) //Return it to the stack
call SaveSoundHandle(st, integer(sh), id, sr.s) //Save it to hashtable
call sr.destroy() //Destroy recycler helper
endfunction
private function Run takes nothing returns nothing
local soundrecycler sr = soundrecycler(GetTimerData(GetExpiredTimer()))
local soundhelper sh = soundhelper(sr.sh)
if not sr.stopped then
call StartSound(sr.s) //Play sound here
endif
if not sh.looping and not sr.stopped then
call TimerStart(sr.t, sh.duration*0.001, false, function Recycle)
else
call sr.destroy()
endif
endfunction
function RunSound takes integer soundRef returns sound
local sound s = null
local integer i = 0
local soundhelper sh = soundhelper(soundRef)
local soundrecycler sr = 0
if soundRef <= 0 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot run sound of undefined type")
return null
endif
//Check if the stack is empty
if sh.sta.peek() == Stack.EMPTY then
//Create a new sound for the stack
set s = CreateSound(sh.fileName, sh.looping, sh.is3D, sh.stopwhenoutofrange, sh.fadeInRate, sh.fadeOutRate, sh.eaxSetting)
//Attach the type to the sound for future reference
call SaveInteger(ht, 0, GetHandleId(s), integer(sh))
call SetSoundDuration(s, sh.duration)
//Stuff that must be performed immediately upon creation of sounds
call SetSoundChannel(s, 5)
call SetSoundVolume(s, 127)
call SetSoundPitch(s, 1.)
if sh.is3D then
//These are settings necessary for 3-D sounds to function properly
//You can change them at will outside of this function
call SetSoundDistances(s, 600., 10000.)
call SetSoundDistanceCutoff(s, 3000.)
call SetSoundConeAngles(s, 0., 0., 127)
call SetSoundConeOrientation(s, 0., 0., 0.)
endif
//Start sound after a delay because it was created here
set sr = soundrecycler.create(s, soundRef)
call TimerStart(sr.t, 0.001, false, function Run)
else
//Allocate a sound from the stack
set i = sh.sta.pop()
if not HaveSavedHandle(st, soundRef, i) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: No sound in given stack member")
return null
endif
set s = LoadSoundHandle(st, soundRef, i)
call RemoveSavedInteger(st, soundRef, i)
call SetSoundVolume(s, 127) //Start volume at max
//Start it here since it wasn't created here
call StartSound(s)
//Recycle the sound in a timer callback after it's finished if nonlooping
if not sh.looping then
set sr = soundrecycler.create(s, soundRef)
call TimerStart(sr.t, sh.duration*0.001, false, function Recycle)
endif
endif
return s
endfunction
function RunSoundOnUnit takes integer soundRef, unit whichUnit returns sound
local sound s = RunSound(soundRef)
call AttachSoundToUnit(s, whichUnit)
return s
endfunction
function RunSoundAtPoint takes integer soundRef, real x, real y, real z returns sound
local sound s = RunSound(soundRef)
call SetSoundPosition(s, x, y, z)
return s
endfunction
function RunSoundForPlayer takes integer soundRef, player p returns sound
local sound s = RunSound(soundRef)
if GetLocalPlayer() != p then
call SetSoundVolume(s, 0)
else
call SetSoundVolume(s, 127)
endif
return s
endfunction
endlibrary
//*****************************************************************
//* STACK
//*
//* written by: Anitarf
//*
//* This is an efficient implementation of a stack in vJass. Since
//* it is based on a linked list, I decided to add common list
//* methods to the data structure so it can function both as
//* a stack and a simple linked list.
//*
//* As a linked list, it has less functionality than Ammorth's
//* LinkedList, but is considerably faster. Note only that most of
//* the list methods have O(n) time complexity so they may not be
//* suitable for operations on very large lists, however due to
//* their simplicity the list would need to be really large for
//* this to become a problem.
//*
//* All stack methods are of course O(1) and as fast as possible.
//* If you just need a stack, this is definitely the best choice.
//*
//* set s=Stack.create() - Instanceates a new Stack object.
//* call s.destroy() - Destroys the Stack.
//*
//* Stack syntax:
//* call s.push(123) - Pushes the value 123 on the stack.
//* A stack may contain multiple
//* instances of the same value.
//* set i=s.peek() - Reads the top value of the stack
//* and stores it to the variable i.
//* set i=s.pop() - Removes the top value from the stack
//* and stores it to the variable i.
//* s.peek()==Stack.EMPTY - Checks if the stack is empty.
//*
//* List syntax:
//* call s.add(123) - Adds the value 123 to the list.
//* A list may contain multiple
//* instances of the same value.
//* s.size - The total number of values on the list.
//* s.contains(123) - Checks if the value 123 is on the list.
//* set n=s.count(123) - Stores the number of times the value
//* 123 is on the list to the variable n.
//* call s.remove(123) - Removes one instance of the value 123
//* from the list. Returns false if
//* the value was not found on the list.
//* call s.purge(123) - Removes all instances of the value 123
//* from the list. Returns the number of
//* values that were removed.
//* set i=s.first - Reads the first value from the list
//* and stores it to the variable i.
//* set i=s.random - Reads a random value from the list
//* and stores it to the variable i.
//* set s2=s.copy() - Makes a copy of the list and stores
//* it to the variable s2.
//* call s.enum(Func,b) - Calls function Func for all values
//* on the list. The function must follow
//* the Enum function interface.
//* b is a boolean value, if it is true
//* then the values will be enumerated
//* top to bottom, if false then bottom
//* to top.
//*****************************************************************
public function interface Enum takes integer value returns nothing
struct Stack
// Use a totally random number here, the more improbable someone uses it, the better.
// This is the value that is returned by .pop and .peek methods and .first and .random operators when called on an empty stack.
public static constant integer EMPTY=0x28829022
// End of calibration.
readonly integer size = 0
private integer top = 0
private static integer free = 1
private static integer array next
private static integer array value
method push takes integer i returns nothing
// Get an index from the list of free indexes.
local integer n=Stack.free
set Stack.free=Stack.next[n]
// Extend the list of free indexes if needed.
if Stack.free==0 then
set Stack.free=n+1
endif
// Store the value to the index.
set Stack.value[n]=i
// Add index to the top of the stack.
set Stack.next[n]=.top
set .top=n
set .size=.size+1
endmethod
method pop takes nothing returns integer
// Get the top index of stack.
local integer n=.top
// Safety check in case a user pops an empty stack.
if n==0 then
debug call BJDebugMsg("stack warning: .pop called on an empty stack!")
return Stack.EMPTY
endif
// Remove the top index from stack.
set .top=Stack.next[n]
set .size=.size-1
// Add the index to the list of free indexes.
set Stack.next[n]=Stack.free
set Stack.free=n
// Return the value.
return Stack.value[n]
endmethod
method peek takes nothing returns integer
// Read the value of the top index.
return Stack.value[.top]
endmethod
method add takes integer value returns nothing
call .push(value)
endmethod
method contains takes integer value returns boolean
// Get the first index of the list.
local integer i=.top
// Search through the list.
loop
// Stop the search when the end of the list is reached.
exitwhen i==0
// Stop the search if the value is found.
if Stack.value[i]==value then
return true
endif
// Get the next index of the list.
set i=Stack.next[i]
endloop
return false
endmethod
method count takes integer value returns integer
local integer count=0
// Get the first index of the list.
local integer i=.top
// Search through the list.
loop
// Stop the search when the end of the list is reached.
exitwhen i==0
// Increase the count if the value is found.
if Stack.value[i]==value then
set count=count+1
endif
// Get the next index of the list.
set i=Stack.next[i]
endloop
return count
endmethod
method operator first takes nothing returns integer
return .peek()
endmethod
method operator random takes nothing returns integer
local integer r=GetRandomInt(1,.size)
// Get the first index of the list.
local integer i=.top
// Loop through the list.
loop
// Stop the loop after a random amount of repeats.
set r=r-1
exitwhen r==0 or i==0
// Get the next index of the list.
set i=Stack.next[i]
endloop
return Stack.value[i]
endmethod
method remove takes integer value returns boolean
// Get the first index of the list.
local integer i1=.top
local integer i2
// Check if the first index holds the value.
if Stack.value[i1]==value then
call .pop()
return true
endif
// Search through the rest of the list.
loop
set i2=Stack.next[i1]
// Stop the search when the end of the list is reached.
exitwhen i2==0
// Check if the next index holds the value.
if Stack.value[i2]==value then
// Remove the index from the stack.
set Stack.next[i1]=Stack.next[i2]
// Add the removed index to the list of free indexes.
set Stack.next[i2]=Stack.free
set Stack.free=i2
set .size=.size-1
return true
endif
set i1=i2
endloop
return false
endmethod
method purge takes integer value returns integer
local integer count=0
local integer i1
local integer i2
// If the first index holds the value, pop it.
loop
// If the list is empty, return.
if .top==0 then
return count
endif
// Repeat until the first index doesn't hold the value.
exitwhen Stack.value[.top]!=value
call .pop()
set count=count+1
endloop
// Get the first index of the list.
set i1=.top
// Search through the rest of the list.
loop
set i2=Stack.next[i1]
// Stop the search when the end of the list is reached.
exitwhen i2==0
// Check if the next index holds the value.
if Stack.value[i2]==value then
// Remove the index from the stack.
set Stack.next[i1]=Stack.next[i2]
// Add the removed index to the list of free indexes.
set Stack.next[i2]=Stack.free
set Stack.free=i2
set .size=.size-1
set count=count+1
else
set i1=i2
endif
endloop
return count
endmethod
method enum takes Enum f, boolean top2bottom returns nothing
local integer array value
// Get the first index of the list.
local integer i1=.top
local integer i2=0
// Populate the array.
loop
exitwhen i1==0
set value91;i2]=Stack.value91;i1]
set i2=i2+1
set i1=Stack.next91;i1]
endloop
// Call the Enum function for each value in the array.
set i1=i2-1
loop
exitwhen i2==0
set i2=i2-1
// Enumerate in which direction?
if top2bottom then
call f.evaluate(value91;i1-i2])
else
call f.evaluate(value91;i2])
endif
endloop
endmethod
method copy takes nothing returns Stack
local Stack that=Stack.create()
// Get the first index of the list.
local integer i1=.top
local integer i2
// Add a dummy index to the top of the new list.
call that.push(0)
set i2=that.top
loop
exitwhen i1==0
// Get an index from the list of free indexes and add it at the end of the list.
set Stack.next91;i2]=Stack.free
set i2=Stack.next91;i2]
set Stack.free=Stack.next91;i2]
// Extend the list of free indexes if needed.
if Stack.free==0 then
set Stack.free=i2+1
endif
// Copy the value to the new index.
set Stack.value91;i2]=Stack.value91;i1]
set i1=Stack.next91;i1]
endloop
// End the new list correctly.
set Stack.next91;i2]=0
// Remove the dummy index.
call that.pop()
// Copy the size value to the new list.
set that.size=this.size
return that
endmethod
method onDestroy takes nothing returns nothing
local integer n
// Remove all remaining indexes from the stack.
loop
// Get the top index.
set n=.top
exitwhen n==0
// Remove it from the stack.
set .top=Stack.next91;n]
// Add it to the list of free indexes.
set Stack.next91;n]=Stack.free
set Stack.free=n
endloop
endmethod
static method onInit takes nothing returns nothing
// Store the EMPTY value to index 0 to make .peek inline friendly.
set Stack.value91;0]=Stack.EMPTY
endmethod
endstruct
endlibrary
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = false
private constant boolean USE_FLEXIBLE_OFFSET = false
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
Cela ne doit pas faire peur, vous n'avez pas besoin de modifier le code de ces trois libraries.
Vous, ce dont vous avez besoin, ensuite, c'est de pouvoir utiliser un son.
L'idée c'est comme pour la méthode "Gui", sauf que vous allez vous servir de SoundUtils comme suit:
Vous avez besoin d'un déclencheur vide que vous aller remplir de cette manière:
Secret:
Jass:
scope MesVariablesSons initializer init
globals
integer SON_SULFATEUSE //l'ID associée au son. On peut en créer autant que l'on voudra jouer de sons différents.
endglobals
public function init takes nothing returns nothing
set SON_SULFATEUSE = DefineSound("Units\\Human\\GyroCopter\\GyrocopterImpactHit1.wav",750, false, true)
//Pendant le chargement de la carte, le son va être initialisé.
//N'oubliez pas de mettre deux barres obliques pour que le chemin fichier soit valide au lieu d'une seule en Gui.
//En gros, ici, vous ordonnez la fonction suivante:
//Régler SON_SULFATEUSE pour être Define Sound(blablabla)
//DefineSound demande les arguments:
//Chemin Personalisé du son
//Durée du son en milièmes de secondes
//Son à jouer en boucle oui/non =>vous, a priori, jamais: le son sera joué à chaque attaque
//Son en 3D: toi, a priori, oui: le son ne sera entendu que lorsque la caméra pendant le jeu est à portée auditive de la tourelle
//Si je traduis set SON_SULFATEUSE = DefineSound("Units\Human\GyroCopter\GyrocopterImpactHit1.wav",750, false, true)
//Ca donne: régler SON_SULFATEUSE comme un son défini pour jouer le chemin fichier "Units\Human\GyroCopter\GyrocopterImpactHit1.wav" pendant 750 milisecondes, pas en boucle, en son 3D
//Vous pouvez créer autant de sons différents que souhaité, mais il faut les régler comme celui-ci
endfunction
endscope
Note: un scope, c'est un genre de déclencheur VJass.
Note: les globals, ce sont comme des variables que tu utilise en Gui. Ici, ce sont des variables globales, mais en VJass.
Et enfin, exactement comme pour la méthode "Gui", quand l'unité attaque, vous jouez le son (ici j'ai mis en condition Tour Canon, mais vous pouvez la remplacer par ce que vous voulez):
Secret:
Gui:
Trigger:
Untitled Trigger 001 Events Unit - A unit Is attacked
Conditions
(Unit-type of (Attacking unit)) Equal to Tour canon
Contrairement à la méthode Gui, vous pourrez jouer autant de sons en même temps que tu vous le voulez. En effet, SoundUtils permet de dépasser les limites des fonctions de base Blizzard sur le son en créant automatiquement des variables pour chaque son joué. Alors qu'en Gui, le même son ne peut pas être joué plusieurs fois en même temps.
Et mêmes avantages que la method Gui: pas besoin d'importer des modèles et de les modifier.
Inconvénient
Bah c'est du VJass, donc ça peut faire peur.
Ensuite on utilise TimerUtils, donc cela demande un peu plus de puissance que les méthodes jouant le son par l'intermédiaire du modèle.
Même chose que la méthode Gui: le son n'est joué que lorsque le projectile atteint la cible.
Même chose que la méthode Gui: il faut trouver les chemins des sons que vous voulez utiliser, et ici, connaître leur durée (on peut le voir via l'éditeur de son quand on clique sur un son en faisant: utiliser comme son).
VII/ Conclusion
A noter que dans le cas d'armes à tir rapide, par exemple, il n'y a pas de recettes miracles. Sur Restricted Complex 601 j'ai adopté la méthode de modification des modèles de projectiles pour que chaque arme ait son propre son de tir. L'inconvénient, c'est qu'avec ne serait-ce que 10 unités qui tirent toutes les 0.4 secondes, la moitié des sons n'est pas entendue, car le moteur de Warcraft III ne gère pas un grand nombre de sons en même temps. Passer par des déclencheurs ou SoundsUtils n'y change absolument rien. Ce qui fait, dans mon cas, que j'ai supprimé les sons des projectiles pour la plupart des armes, puisqu'on entendait absolument pas le quart des sons des unités etc.
J'espère que ce didacticiel pourra vous aider. L'exemple des projectiles peut bien sûr s'appliquer à tout son du jeu: effet spécial, unité, etc... _________________
Dernière édition par jk2pach le 12/08/10 13:11; édité 1 fois
Inscrit le: 21 Fév 2010 Messages: 1785 Sujets: 22 Spécialité en worldedit: La modestie Médailles: 1 (En savoir plus...)
Posté le: 12/08/10 12:17 Sujet du message:
Dans ton dernier déclo, tu mets "(Unit-type of (Triggering unit))" au lieu de "Attacking unit". Sinon, je pense que les sons, avec les méthodes déclencheur, sont joués au début de l'attaque, et pas quand le projectile atteint sa cible.
A part ça, très bon tuto, j'ai appris plein de trucs . _________________
Pour les sons, non. Par déclencheur, c'est le moment d'atteinte du projectile qui compte.
Pour ça que la méthode d'incorporer le son dans un modèle est un peu plus précise, suffit de mettre l'event object au moment de l'animation "Birth". _________________
Je me permets de rebondir pour poser une petite question:
Je pense que ce n'est pas possible mais dans le cas d'un effet spécial ou d'un buff, y a-t-il moyen de dissocier le son de l'effet que l'on joue?
Inscrit le: 21 Fév 2010 Messages: 1785 Sujets: 22 Spécialité en worldedit: La modestie Médailles: 1 (En savoir plus...)
Posté le: 30/01/15 17:45 Sujet du message:
La plupart du temps, non : le son est dans le modèle de l'effet spécial. Il y en a quelques-uns qui sont personnalisables dans l'éditeur d'objet ("Infographie - Son", si jeune mabuse) mais sinon, il faut passer par un logiciel 3D.
Avec War3Model Editor, par exemple, tu peux trouver les sons dans le panneau des "nodes". _________________
Ce n'est pas le son en lui-même qui est dans le modèle, seulement un chemin. Le fichier de son est accessible et donc jouable séparément. Pour désactiver un son d'un effet spécial sans lui changer le modèle tu peux toujours remplacer dans la carte via importateur le fichier son par un fichier vide. _________________
Cependant, si on applique ta méthode, cela signifie que si l'effet est joué dans son véritable contexte en cours de partie, il n'y aura plus de son associé, ce qui peut être problématique.
Le cas que je voulais soulevé est celui de l'aura. Pour une aura j'utilise un effet, que je fais se répéter sur mon unité. Mais au final il est assez désagréable d'entendre en permanence le son du "birth" se répéter. _________________
Page 1 sur 1 La question posée dans ce topic a été résolue !
Vous ne pouvez pas poster de nouveaux sujets dans ce forum Vous ne pouvez pas répondre aux sujets dans ce forum Vous ne pouvez pas éditer vos messages dans ce forum Vous ne pouvez pas supprimer vos messages dans ce forum Vous ne pouvez pas voter dans les sondages de ce forum