Program Structure

Find quick help here to get you started with Hollywood
ocean77
Posts: 111
Joined: Mon Jan 28, 2019 8:34 pm

Program Structure

Post by ocean77 »

I am working on my little game (an interactive visual novel) in Hollywood. I have absolutely no experience of value when it comes to programming, so I've done a lot of work in Designer. However, WinUAE keeps crashing on me in Linux and I found it extremely frustrating.

So I decided to learn programming. I found Free Pascal and Lazarus relatively easy to learn the basics of, but when I returned to the Hollywood language I found myself confused. I just don't understand the structure. It seems like anything goes.
How can I identify the main procedure? How do I know what to call and when from where?
While the instructions themselves seem easy enough I'm having a hard time understanding how to build understandable code.

Any hints and tips?
ocean77
Posts: 111
Joined: Mon Jan 28, 2019 8:34 pm

Re: Program Structure

Post by ocean77 »

Here's a piece of reference code with some questions in comments:

Code: Select all

@VERSION 8,0
@DISPLAY{WIDTH = 1280, HEIGHT = 720, MODE = "WINDOWED", TITLE = "The Beckoning Test", COLOR = #BLACK}

; *** VARIABLES ***
;------------------

Stage = 0

;-----------------------------------------------------------------------------
;-----------------------------------------------------------------------------

; *** MAIN MENU FUNCTION ***
;---------------------------

Function p_MainMenu()

    @BGPIC 1, "MenuTitle.jpg"
    @SAMPLE 1, "cmp_select.wav"
    @SAMPLE 2, "cmp_confirm.wav"
    SetFont(#MONOSPACE, 64)
    SetFontStyle(#ANTIALIAS)
    SetFontColor(#GREEN)

    TextOut(#CENTER + 400, #CENTER, "play")
    TextOut(#CENTER + 400, #CENTER + 70, "options")
    TextOut(#CENTER + 400, #CENTER + 140, "quit")
    
MakeButton(1, #SIMPLEBUTTON, #CENTER + 400, #CENTER, 120, 70, {OnMouseOver = p_MenuEventFunc}) ; Why is the code inside the curly brackets needed as long as the following line is present? :

InstallEventHandler({OnMouseOver, OnMouseDown = p_MenuEventFunc})
    
EndFunction

Function p_MenuEventFunc(msg)
    
    Switch msg.action
    Case "OnMouseOver":
        PlaySample(1)
    
    Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
    EndSwitch
    
EndFunction

;-----------------------------------------------------------------------------

; *** STAGE 01 FUNCTION ***
;--------------------------

Function p_Stage01()

    @BGPIC 2, "Stage01.jpg"
    DisplayBGPic(2)
    TextOut(#CENTER, #CENTER, "Stage 01")
    
  ; I used the same button setup here for testing purposes. Even if it's still "Button 1" It works as expected. I suspect it is because it belongs to this
  "object", is that a correct assumption?
    
MakeButton(1, #SIMPLEBUTTON, #CENTER, #CENTER, 120, 70, {OnMouseOver = p_MenuEventFunc}) ; Why is the code inside the curly brackets needed as long as the following line is present?:

InstallEventHandler({OnMouseOver, OnMouseDown = p_MenuEventFunc})
    
EndFunction

;-----------------------------------------------------------------------------


; I call this the main loop, but I don't know why.

;*** MAIN LOOP *** 
;-----------------


If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf

EscapeQuit(True)


Repeat
        WaitEvent
Forever
p-OS
Posts: 167
Joined: Mon Nov 01, 2010 11:56 pm

Re: Program Structure

Post by p-OS »

In Pascal , C, E etc. you define all your functions and procedures first, then you declare your main() function.

The compiler creates code that starts to execute where main() is.


In Hollywood however, your .hws Script itself is the main() function !

example:
given be example.hws:

Code: Select all


;declaring a function
Function greet()
  Print("Hello !")
EndFunction

;next, first code to be executed immediately

greet()

you could think as:

Code: Select all

;; Function main()

    ;declaring a function
    Function greet()
      Print("Hello !")
    EndFunction

    ;next, first code to be executed immediately
  
    greet()

;;; EndFunction
Despite Pascal Hollywood is an interpreter language.

Thus code is simply executed from the beginning of your .hws file.
First, function declaration is executed, then the code beneath it.

Functions itself are created at runtime !
They are declared within your main() .


HW is not only an interpreting language. As it is based on Lua, it also is a functional language.
The keyword function in fact is nothing else than a function itself, that returns a value of datatype function, actually a pointer to the code defined between function and endfunction.

Instead of

Code: Select all

    Function greet(name)
      Print("Hello " .. name .. "!")
    EndFunction
you could instead write

Code: Select all

    greet=Function(name)
      Print("Hello " .. name .. "!")
    EndFunction

This behaviour has some consequences:
- syntax errors within a function will not get obvious prior to the first time it is actually called at runtime
- you can pass a function as parameter to another function
- functions must always be declared in the script before they are called
- you can assign a function to another variable any time:

Code: Select all

    greet=Function(name)
      NPrint("Hello " .. name .. " !")
    EndFunction
    
     sayhallo=greet
     
     greet("ocean77")
     sayhallo("ocean77"
will output:

Code: Select all

Hello oecan77 !
Hello oecan77 !

Some another important notes about HW:
- except function parameters all variables are global per default, even when declared within a function. To make them local use the keyword local
- variables have no datatype. The value currently assigned instead has a datatype
- functions can be nested
- procedures don't exist, Simply declare a function that doesn't return a value
- functions can have more than one return value !
- the HW "Compiler "actually binds all your files , i.e. your .hws, alle linked images and the HW interpreter/player to one executable. Nonetheless your code ist still interpreted and not native code !
Bugala
Posts: 1168
Joined: Sun Feb 14, 2010 7:11 pm

Re: Program Structure

Post by Bugala »

Code: Select all

MakeButton(1, #SIMPLEBUTTON, #CENTER + 400, #CENTER, 120, 70, {OnMouseOver = p_MenuEventFunc}) ; Why is the code inside the curly brackets needed as long as the following line is present? :

InstallEventHandler({OnMouseOver, OnMouseDown = p_MenuEventFunc})
I think you are misunderstanding here.

You can change this line:

Code: Select all

InstallEventHandler({OnMouseOver, OnMouseDown = p_MenuEventFunc})
into this:

Code: Select all

InstallEventHandler({OnMouseDown = p_MenuEventFunc})
When you are doing InstallEventHandler, you are doing something which checks if mousebutton is down, regardless where it is. I guess your intention is to check if mousebutton have been pushed down on top of a button, but this is not what it does.
What it currently does is that regardless where you push the mouse button down, it will always execute p_MenuEventFunc.

I don't think using InstallEventHandler with OnMouseOver is actually even possible, since InstallEventHandler wouldn't specify on over what it is. If it works, in that case I guess it would always happen regardless of MouseOver what happens.

Reason you need this OnMouseOver = P_MenuEventFunc inside the MakeButton is, that this MouseOver is specific to this specific button only. That if Mouse is Over this specific Button, then what happens? And now you have defined that in that case happens the p_MenuEventFunc.

Code: Select all

Stage = 1 		; Updating this variable here doesn't work, why?
Problem with this is how you have programmed the rest. Stage becomes 1, but it never affects anything.

Code: Select all

If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf
This is the part where I suppose you think the Stage = 1 should have some effect.
It would have effect here indeed, but this part is never executed again.

You execute this once at beginning, when Stage = 0, and then this part never happens again when you run your code.

What happens repeatedly in your code is following:

Code: Select all

Repeat
        WaitEvent
Forever
That is your main loop. And your main loop in practice consist only of two things.

If mouse is being clicked anywhere (thanks to InstallEventHandler) it will execute p_MenuEventFunc with msg being the "OnMouseDown".
If Mouse is Over the Button 1 (the only button in program) then p_MenuEvenFunc with msg being "OnMouseOver" is being executed.

Nothing else ever happens in your program.

By other words, your program does change Stage=1, but it never uses that Stage=1 situation anywhere, since only place where it would use is that If-End place, which is executed only once at beginning, never again after program have started.
Bugala
Posts: 1168
Joined: Sun Feb 14, 2010 7:11 pm

Re: Program Structure

Post by Bugala »

By the way, if you want it to work, you could do it like this:

Change:

Code: Select all

;*** MAIN LOOP *** 
;-----------------


If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf

EscapeQuit(True)


Repeat
        WaitEvent
Forever
into

Code: Select all

;*** MAIN LOOP *** 
;-----------------

Function Change()
If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf
endfunction

change()

EscapeQuit(True)
and

Code: Select all

Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
    EndSwitch
into

Code: Select all

Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
        change()
    EndSwitch
By other words, you change you If-Endif loop at beginning into a function, which you will call once at beginning (at this point it actually works exactly like beginning, just through a function instead of direct lines)

Then you go to you OnMouseDown Case and add change() function to be executed once more after stage have been changed into 1. In this case it will go through the same If-EndIf loop, but this time with Stage being value 1.
ocean77
Posts: 111
Joined: Mon Jan 28, 2019 8:34 pm

Re: Program Structure

Post by ocean77 »

Thank you both for taking the time to give those in-depth explanations. I appreciate it.
Not sure I completely understand everything yet, but I'll get there eventually.

Bugala, You suggestions worked beautifully. Thanks.
ocean77
Posts: 111
Joined: Mon Jan 28, 2019 8:34 pm

Re: Program Structure

Post by ocean77 »

How would I go about hiding a brush after a certain event.

I want to highlight a part of a background picture using a brush once the mouse is over that area and return to normal when the mouse exits.

As far as I know I have to enable layers, but where? In that particular function, or in the main one?

Can't get it to work, either way.
Bugala
Posts: 1168
Joined: Sun Feb 14, 2010 7:11 pm

Re: Program Structure

Post by Bugala »

Hollywood has from a programming point of view two different approaches to how it handles brushes.

The first approach is that you have to draw all the graphics to screen each cycle. That basically when cycle starts, the screen is black, and if you don't draw anything, it also will stay black. That regardless how much graphics you drew last cycle, there will be nothing at start of next cycle.

The second approach is the LAYER approach. In this case, graphics are like real-life stickers on a table. You have a sticker, and you can place it on your table and it stays there until someone moves it around or removes it. on the LAYER approach this is how brushes work too. You once tell you are placing a brush at location x, y, and it will stay there until you move or remove it.

Both approaches have their pros and cons, but in your case I recommend using the second, the LAYER approach, since it is easier for a beginner and you don't need to take care of so many things.

To explain this from code point of view, the first approach would basically make following changes to your code:

This

Code: Select all

Repeat
        WaitEvent
Forever
would change into something like this:

Code: Select all

Function UpDateGraphics()
	if MyFirstBrushOn = true then DisplayBrush(1, x, y)
	if MySecondBrushOn = true then DisplayBrush(2, x, y)
endfunction

Repeat
	UpDateGraphics()
        WaitEvent
Forever
Notice that this example isn't a working version, since there are things that need to be taken care of before this would work, I am just explaining the principle here.

On your "OnMouseOver" and "OnMouseClick" functions, you would do something like this:
This

Code: Select all

 Switch msg.action
    Case "OnMouseOver":
        PlaySample(1)
    
    Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
    EndSwitch
would change into this:

Code: Select all

 Switch msg.action
    Case "OnMouseOver":
        PlaySample(1)
        MyFirstBrushOn = True
    
    Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
        MyFirstBrushOn = False
    EndSwitch
idea is that Your Repeat-Forever loop would continuously keep repeating "UpDateGraphics()" function each cycle, which would keep checking what is the state of MyFirstBrushOn and MySecondBrushOn and depending if they are TRUE or not, would depend if they would be drawn to the screen.

When mouse moves to OnMouseOver, it would change MyFirstBrushOn into TRUE, and after that that DisplayBrush(1, x, y) would be executed each cycle, resulting in it looking like there is stable graphic image on screen.

When MouseClick would change MyFirstBrushOn to FALSE, this "UpDateGraphics()" function would stop executing DisplayBrush(1, x, y) command each cycle, resulting it seeming like it is disappearing, since it is simply not drawn anymore.


Now the LAYER approach is simpler in its approach, and the example code I am going to show, is basically a working one.

This code will stay as it is already, since when using LAYERs, Hollywood takes care of drawing brush every cycle (or you could say, frame)

Code: Select all

Repeat
        WaitEvent
Forever

But this:

Code: Select all

 Switch msg.action
    Case "OnMouseOver":
        PlaySample(1)
    
    Case "OnMouseDown":
        PlaySample(2)
        Stage = 1 		; Updating this variable here doesn't work, why?
    EndSwitch
Would change into:

Code: Select all

 Switch msg.action
    Case "OnMouseOver":
        PlaySample(1)
        ShowLayer(1)
    
    Case "OnMouseDown":
        PlaySample(2)
        HideLayer(1)
        Stage = 1 		; Updating this variable here doesn't work, why?
    EndSwitch
And you are almost done. For there are two things you still need to do, but to explain, idea is that you have a LAYER with ID 1 that you wish to show/hide.

After you have placed it somewhere (which we will do next), after that you can simply tell that you wish to either show layer id 1, or hide layer id 1, and it will happen.

To make this work, you need to add basically two lines to beginning of your main code:

To this:

Code: Select all

;*** MAIN LOOP *** 
;-----------------


If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf

EscapeQuit(True)


Repeat
        WaitEvent
Forever

You will add TWO lines to beginning, changing it into this:

Code: Select all

;*** MAIN LOOP *** 
;-----------------

EnableLayers()
InsertLayer(1, #BRUSH, 2, x, y, true)

If Stage = 0
    p_MainMenu
ElseIf Stage = 1
    p_Stage01
Else
    End

EndIf

EscapeQuit(True)


Repeat
        WaitEvent
Forever
EnableLayers() means that from now on you can use those layers. If you try to use command "InsertLayer" before first doing EnableLayers(), Hollywood will complain that Layers are not on.

The InsertLayer command is a bit more complicated.

First of all, when using LAYERS, every type of graphic, that is video, Brush, Anim, TextObject, are all considered as same type - a Layer.

This means that instead of using different commands to display them, like DisplayBrush or TextOut, you will simply be using LAYER commands to do their stuff.

Notice however that you can still keep using, for example, TextOut command, even Layers are turned on.
Just that understand that Layer can be Brush, text, video, anim - anyone of those, and when you use ShowLayer(n), it might result in brush, video, anim or text being shown, depending what LAYER id n happens to be containing.


When using InsertLayer command, the you first tell which ID number it will take. In that example case I decided to use ID spot 1, it however could be any other number as well, like 20.

next I am using #BRUSH, this will tell Hollywood that this Layer is actually a brush, instead of Anim, Video, or TextObject.

and after that comes another number, in this case I am using number 2. This means that it will Load Brush ID number 2 to be used as the brush.

Point being that you first need to create a brush ID 2, for example by using LoadBrush(2, "filename"), for if Brush ID 2 doesn't exist, then InsertLayer cant use it.

Then I tell its x and y co-ordinates on the screen, and last option is true/false. If I use TRUE, it means that Layer will start on hidden state. using FALSE would display that layer instantly.

After this I can simply use, ShowLayer(1), and it will start displaying this Brush I used to put to that Layer ID 1 using the InsertLayer command in the co-ordinates I set when using InsertItem.

And there you go, now this program works.

Well almost. Since naturally you do need to create that Brush id 2 first. One way would be to use:

@BRUSH 1, "MenuTitle.jpg"

similarly how you are now using @BGPIC
ocean77
Posts: 111
Joined: Mon Jan 28, 2019 8:34 pm

Re: Program Structure

Post by ocean77 »

Thank you, you are a gem.

But the layer will not hide once the mouse leaves the hotspot area.

Code: Select all

@VERSION 8,0
@DISPLAY{WIDTH = 1280, HEIGHT = 720, MODE = "WINDOWED", TITLE = "The Beckoning - Playable Teaser", COLOR = #BLACK}

; *** VARIABLES ***
;------------------

Stage = 0       ; Set Start Location

VolMus = 10     ; Set Ambience/ Music Volume
VolSnd = 10     ; Set Sound Effect Volume

HubToolsOpen = "False"
HubFilesOpen = "False"
HubCommsOpen = "False"

;----------------------------------------------------------------------------
;----------------------------------------------------------------------------

; *** MAIN MENU FUNCTION ***
;---------------------------

Function p_MainMenu()
    SetMusicVolume(1, VolMus)

    @BGPIC 1, "MenuTitle.jpg"
    @MUSIC 1, "DroneRoom.ogg"
    DisplayBGPic(1)
    PlayMusic(1, 0)
    @SAMPLE 1, "cmp_select.wav"
    @SAMPLE 2, "cmp_confirm.wav"
    SetFont(#MONOSPACE, 64)
    SetFontStyle(#ANTIALIAS)
    SetFontColor(#GREEN)

    TextOut(#CENTER + 400, #CENTER, "play")
    TextOut(#CENTER + 400, #CENTER + 70, "options")
    TextOut(#CENTER + 400, #CENTER + 140, "quit")
    
MakeButton(1, #SIMPLEBUTTON, #CENTER + 400, #CENTER, 120, 70, {OnMouseOver = p_MenuEventFunc})

InstallEventHandler({OnMouseOver, OnMouseDown = p_MenuEventFunc})
    
EndFunction

Function p_MenuEventFunc(msg)
    
    Switch msg.action
    Case "OnMouseOver":
        PlaySample(1, {Volume = VolSnd})
    
    Case "OnMouseDown":
        PlaySample(2, {Volume = VolSnd})
        StopMusic(1)
        Stage = 1
        Change()
    EndSwitch
    
EndFunction

;-----------------------------------------------------------------------------

; *** STAGE 01 FUNCTION ***
;--------------------------

Function p_Stage01()
    
    SetMusicVolume(2, VolMus)

    @BGPIC 2, "Stage01.jpg"
    @MUSIC 2, "Ambience_01.ogg"
    @BRUSH 1, "terminal_hilight.png"
    DisplayBGPic(2)
    PlayMusic(2, 0)
    
MakeButton(1, #SIMPLEBUTTON, 20, 250, 120, 120, {OnMouseOver = p_Stage01EventFunc})

InstallEventHandler({OnMouseOver, OnMouseDown = p_Stage01EventFunc})

EndFunction

Function p_Stage01EventFunc(msg)
    
    Switch msg.action
    Case "OnMouseOver":
        
        ;DisplayBrush(1, 0, 209, {LoadAlpha = True})
        
        InsertLayer(1, #BRUSH, 1, 0, 209, TRUE) /* If I put this in the main function the layer shows up on the first screen. Also, the background not complety transparent as the frame around it shows. */
        ShowLayer(1)
        
        PlaySample(1, {Volume = VolSnd})
        
    Case "OnMouseOut":
        HideLayer(1) ; Does not hide the layer.
    
    Case "OnMouseDown":
        PlaySample(2, {Volume = VolSnd})
        StopMusic(2)
        Stage = 0
        p_MainMenu()
    EndSwitch
    
EndFunction
    
;----------------------------------------------------------------------------



;*** MAIN LOOP *** 
;-----------------

EnableLayers()

Function Change()
    If Stage = 0
        p_MainMenu
    ElseIf Stage = 1
    
        p_Stage01
    Else
End

EndIf
EndFunction

Change()

EscapeQuit(True)

Repeat    
    WaitEvent
Forever
I know the script is total a mess, but there you go... :roll:

EDIT: I added OnMouseOut to the event handler, because I'd forgotten and I thought that may be the problem. But it had no effect.
Bugala
Posts: 1168
Joined: Sun Feb 14, 2010 7:11 pm

Re: Program Structure

Post by Bugala »

You are on a right track, but there is one fault with your program currently, which is very easy to fix, but I am explaining the problem.

You now have this function "p_Stage01EventFunc(msg)" which has OnMouseOut case there. This is right.

Problem is, the idea is that this function is being called when one of the things happen, like "OnMouseOver". The idea is that the variable "msg" tells which case it is, if it is OnMouseOut, or OnMouseOver, or something else.

However, this info for msg comes from outside.

That when you move mouse over a button it calls for this function, and it also sets msg to be "OnMouseOver".

However, right now nothing calls that function with msg being "OnMouseOut", since you have set nothing to call it with that msg.

This one here:

Code: Select all

MakeButton(1, #SIMPLEBUTTON, 20, 250, 120, 120, {OnMouseOver = p_Stage01EventFunc})
Tells that when mouse moves over this button, it will call "p_Stage01EventFunc" and it will set msg as "OnMouseOver".
However, there is nothing that tells what happens if "OnMouseOut" happens for this button. So right now nothing happens.

To fix this, simply change this line into:

Code: Select all

MakeButton(1, #SIMPLEBUTTON, 20, 250, 120, 120, {OnMouseOver = p_Stage01EventFunc, OnMouseOut = p_Stage01EventFunc})
And now you have two different things your button is looking for.

it looks if Mouse is put over the button, in which case it calls that Function with msg set to "OnMouseOver", it will also now look if Mouse is moved away from top of the button, in which case it calls the same function, but this time setting msg as "OnMouseOut".

If your code doesn't have any other problems, this should now work (I didn't try your code, just noticed that you were missing this OnMouseOut call)
If it doesn't work, let me know and I take a better look.
Post Reply