Doc:2.6/Tutorials/Frijoles

提供: wiki
< Doc:2.6‎ | Tutorials
2018年6月29日 (金) 05:48時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

Frijoles: The simple Blender Game Engine Demo

Feast your ojos on Frijoles ("free hoh lehs"), the nearly-pointless game where you control three unfortunate brothers legume caught in an avalanche.

Angry boulders fall from the top of the level -- you will have to first dodge them, then later springboard off of them to climb high enough to escape. You are penalized for time, but rewarded for keeping the beans moving.

Download (~2MB) here: Tutorials-Frijoles.blend

Press P to play, Esc to Escape, and switch the screen layout to Game Logic to start exploring the innards.

Tutorials-Frijoles-Screenshot.png

Understanding It

The basic idea is that you take your basic Blender objects and add to them:

  • Physics properties (such as their shape to define collisions)
  • Logic bricks (such as adding a special action upon collision)
  • Python code (really only a special (and infinitely customizeable) logic brick)

These tools are useful in almost every use of the BGE. This game hopes to serve as a minimal introduction to a handful of these features. All five basic Physics Types are present, as are several of the bricks and the various ways of connecting bricks, as is a bit of Python.

If you have not done any work with the Blender Game Engine (BGE), you might want to first check out the Domino Tutorial.

For reference and elaboration of the physics properties, see the BGE Physics root page.

Implementation

The simplest way to take a tour of the system is to go through each of the objects.

For this experience it is recommended to Ctrl⇧ ShiftS a new copy of the ファイル:Tutorials-Frijoles.blend and tinker away. Several times we will note a variant logic configuration, and it can be informative to play with these settings and see them actually have their effect. These variants are noted with this "change" icon: Tutorials-Frijoles-Tweakable.png

Blender3D FreeTip.png
Note
Each of the following section headings corresponds directly to the object name in the .blend. When in the Game Logic screen layout you can select the objects either in the 3D Viewport or in the Outliner and see the logic bricks that belong to it.


"HUD"

To demonstrate the absolute simplest of objects, this Heads-Up Display is a No Collision object. It's only there for display purposes, so there is no need for the Bullet physics engine to process it for collisions, gravity, etc.

"ArenaFloor"

The ArenaFloor's Physics properties. The most important setting is the Physics Type, in this case Static

This is your basic "Static" object. If you select this object then look at the Properties Editor to get to the Physics button on the right (perhaps scrolling the buttons with your mouse-wheel), you will see its settings as in the image on the right. The Static physics type will collide, but it will not move -- it acts like a brick wall.

Our custom property, is_springboard

There are no logic bricks on this object, because it does nothing other than serve as a platform. There is one thing to notice, however, and that is its "Game Properties". This is found in what is actually the Logic Editor's N Properties Panel, on the left (see the second image). Properties like this are accessible in bricks and in Python (as "own['some_prop']"). Here, we use this property in our "Guy" objects' Collision Sensors to specify that it is a legal object to jump off of. More on this in the following section.


"Guy.000", "Guy.001", and "Guy.002"

These beans are a Dynamic type. This means that they will collide, but they will not rotate upon collision. If you doubt this decision, set one of your Guy.00_'s to Rigid Body. Tutorials-Frijoles-Tweakable.png ...Yeah, see why we didn't use that?

These are our first objects with logic bricks, and they even include some Python.

Each of the three Guys is a duplicate of the others. Note that even if you use AltD to create linked duplicates, the logic bricks will be independent across the objects. In a case like this we want them to behave identically, though, so the workaround is to select out-of-date objects then Shift-select the one with your new changes, then hit Space » Copy Logic Bricks to Selected. While we're at it, this is a good time to also point out Space » Copy Game Physics Properties to Selected.

You will notice the Logic Editor is divided into three columns:

  • Sensors - These produce pulses based on external events, whether that be a collision, a message from another object, a clock tick, etc.
  • Controllers - These handle the pulse from one or more Sensors. They are basically boolean operators (And, Or, etc.) but they also include Python.
  • Actuators - These "do stuff," to include moving objects, initiating Action animations, switching Scenes, and adding/removing Object.

This 2.4x BGE Doc did a great job of describing the three Brick types plus Game Properties:

  • The Sensors are like the 5 senses
  • The Controllers are like the brains
  • The Actuators are like the muscles
  • The Properties are like the memories
The first two rows of bricks for the Guy objects, which turn left and turn right across a 180º fan.

The first two bricks to look at are the Keyboard Sensors. If you expand their contents by clicking the arrow to the left of the name, you will see that we have a "Left Arrow" and a "Right Arrow" pair.

Follow the noodle across to the right and you will see it connects to a simple "And" Controller. These controllers look at all the incoming Sensors and make the decision to pass on the pulse or not. Since there is only one noodle incoming to its socket, there is nothing to "And" with, therefore it will pass straight through to the Actuator on the right. You could use other types, but "And" is fairly standard as the default for when you only have one input.

The noodles branching to the Actuators demonstrate how one output socket can branch to many input sockets. Both Controllers connect to both the Constraint Actuator and a respective Motion Actuator. The Constraint here is simply to keep the beans from over-rotating. This limit was actually a late addition in response to feedback from frustrated users (though the author felt full rotation provided a good layer of difficulty -- Simply click the "x" at the top-right of the Constraint actuator to invoke King Mode! Tutorials-Frijoles-Tweakable.png). The lower branch of the logic flow is the Motion Actuator, which provides a 10º rotation in either direction around the z-axis.

Blender3D FreeTip.png
Brick print()-Debugging
If you are used to programming in conventional environments, you know about throwing in a print-statement to verify your assumptions. One quick way to double-check that the Sensor-Controller-Actuator chains are being pulsed as expected: Add some crazy "Motion" Actuator and hook it up identically to the Actuator you are unsure about. When you see your object spinning wildly, you can be sure that the chain is firing.


The next row of bricks begins with a Collision Actuator that is set to Property: is_springboard, as mentioned above in "ArenaFloor". This Sensor will pulse only when the Guy collides with an object that has the is_springboard property. To enable Total Cheater Mode, you can blank out this "is_springboard" value and watch the beans race to immediate victory (because they are colliding with the "ArenaBacking" below them) Tutorials-Frijoles-Tweakable.png. The connection is again through an And Controller on to a Motion Actuator, but this time the Motion is Type: Servo Control. It adds 3.0 units of linear velocity along the local y-axis. Note the fields in the middle of the brick that limit the force. Currently the max for y is 100---if you take this off you get some interesting behavior when Guys get trapped: the force builds and builds until they suddenly uncork.

The final row for this object is an Always Sensor that pulses once every ten ticks. This is the fatigue and jump bonus pulse. You will see the top "And" goes to a Message Actuator. This Actuator will trigger a pulse on the "LifeBar" object (below) with the subject "Fatigue", letting it know that the bean experienced one unit of fatigue. This adds a perpetual amount of health drain -- but it is offset in proportion to the bean's speed. So, if your Guy is stuck on a boulder, or if you are huddling in the corner trying to stay safe, your life will deplete, but if you are rapidly moving around it will regenerate. Since this "speed" calculation is not available in brick form, we are introduced to our first Python Controller.

If you look at the Controller's value, it is set to Script: "send-speed.py". If you then switch the Text Editor buffer to "send-speed.py", you will see a short script:

import bge
cont = bge.logic.getCurrentController()
own = cont.owner
speed = own.getLinearVelocity().magnitude
bge.logic.sendMessage('Jump', str(speed), 'LifeBar')

For those interested in using Python, the first concept to notice is that it imports bge, not bpy (which is used to program in Blender's internal API). The two APIs are completely separate:

  • You cannot use bpy from within the BGE (because it would require the presence of the whole Blender system, which you cannot guarantee), and
  • The bge module cannot be used from within non-BGE Blender (because it requires, for example, the creation of physics meshes for each object, which currently only the BGE can do).

This first gets a handle to the Controller, then gets a handle to the controller's owner (whichever "Guy.00_" object that owns these bricks). From here the entire API is at your disposal. For this bit we only need to calculate the speed and then send that as a message to the "LifeBar". The bge.logic.sendMessage() call is directly equivalent to using a Message brick. Notice that the subject is now "Jump", which will trigger a different Message Sensor than did "Fatigue". It also passes the speed as the "body" of this message.

Other than that, the only thing to note about Guys is that they have the "main_character" property set -- which is to help the "FinishLine" detect a collision only from the characters, and not the boulders.

"Boulder.small", "Boulder.medium", and "Boulder.large"

The Boulder objects are of the more interesting Physics Type: Rigid Body. This means that they will not only collide, but also torque in response. We added no special logic to make the Boulders roll and tumble down the Arena, we simply set the to Physics Type: Rigid Body and place them at the top of the level to let Bullet Physics do the rest of the work.

These are tucked away on Layer 2, which is because of the rule for Edit Object's "Add Object" mode, which requires the spawned objects to come from a hidden layer. This is convenient, anyway, because we don't want them to show unless they are active in the game.

The Boulders have two lines of logic, one very odd and one very simple.

The odd one is the Always Actuator named "MotionCheck". The purpose is to forcibly suspend dynamics for Boulders that have settled down. If you want to see the effect that this has, cut its noodle (with CtrlLMB Template-LMB.png across the noodle) and look at two things:

  • Enable Game » Show Physics Visualization - You will see that the boulders fall down, and their collision mesh might turn from white (active) to green (sleeping) briefly, only to be awoken by new Boulders from above.
  • Enable Game » Show Framerate and Profile - The time devoted to the "Physics" portion will start out minuscule at the beginning of the game, and as more and more (non-sleeping) Boulders accumulate, this number will climb to nearly 200x its original value, causing a noticeable slowdown.

The implementation is through a Python Controller which calls a script named "boulder-speedcheck.py". The key lines are these:

import bge
cont = bge.logic.getCurrentController()
own = cont.owner
speed = own.getLinearVelocity().magnitude
own['top_speed'] = max(own['top_speed'], speed)
if (1 < own['top_speed'] and speed < 0.1):
        cont.activate(cont.actuators['Staticize'])
        cont.activate(cont.actuators['Sleepy'])

The basic idea is that we want to suspend dynamics whenever the speed is below a certain threshold, but of course not when it is first starting out (when its speed will also be very low). The code calculates the boulder's speed then keeps a record of the top speed in the object's top_speed property. The if-statement checks to make sure the Boulder has fallen far enough at least to get a speed of "1", then it checks to see if the Boulder has slowed down below the limit of "0.1". If so it triggers both Actuators connected to the right of the Controller.

You can see that this is how a Python Controller can selectively pass or block pulses in a manner similar to the regular, boolean Controllers - if you call cont.activate() on the Actuator, that Actuator will run.

So, this Controller enables two Actuators. The first one is the Edit Object to Suspend Dynamics. This is like turning it into a Static Physics Type.

The second Actuator is the weird one. It sets the State from 1 to 2. This basically disables all of the Controllers on this object, because they are set to work only in State 1. (To see this look closely at the Controller for the dropdown that says "1". If you do change this to something else, your Controller will disappear, but you can use the State Panel to see these other Controllers.

And finally, we have a basic Collision Actuator that runs the Python Controller for "send-pain.py". This script is almost identical to "send-speed.py", above, except it results in a Message with subject "Ouch" and it multiplies the speed times the mass of the Boulder--more mass means more pain to the Guys. This "Mass" is a Physics property, under Physics » Attributes » Mass. It will also affect the inertia of collisions.

"BoulderSpawnPoint"

Now that we have some Boulder objects, let's pepper them down on the hapless Frijoles.

We need a point in 3D space to spawn the new objects, but we don't want it to do anything else as far as graphics or physics, so we use an Empty. The Empty will patrol left and right, randomly spawning one of the three Boulder sizes as it goes. To achieve this random effect, we could have used a Python controller, but it is more interesting to demonstrate a use of the Expression Controller.

The spawn logic.

Notice that the (otherwise identical) Sensors are named Random, Random1, and Random2. The first one goes straight through the And Controller and on to the Edit Object Actuator which does an "Add Object" for the "Boulder.small".

The Random1 Sensor connects to the Expression Controller, which is set to "Random1 and not Random". This means that if both Random1 and Random pulse at the same time, the Controller's expression will evaluate to false and will not send the pulse along to its Actuator.

The same idea goes for Random2, which has "Random2 and not Random1 and not Random".

So, in the event of a tie, where more than one Random Sensor pulses, the smaller Boulder is spawned. The preference for the smaller Boulders is not very noticeable, but if you rework the Controller logic to not do these checks at all, the effect is very noticeable: sometimes Boulders spawn at the same point simultaneously, which causes them to violently collide and spray out (sometimes completely out of the Arena). This increases difficulty and zaniness, so you might want to try it in this configuration.Tutorials-Frijoles-Tweakable.png

The remaining logic for "BoulderSpawnPoint" is actually a good demonstration of Brickheaded thinking. Though, again, you could use Python for it, it works out fairly cleanly when implemented in pure bricks. The basic idea is that the object has a "leftward" Boolean property which causes it to move leftward until it is near to one of the Arena's walls, which causes "leftward" to toggle and subsequently the motion becomes rightward.

So the top brick is the Near Sensor named "HitWall", which collides only with the object that has the "is_arena" property at a Distance of 0.02. If you make this Distance too low, sometimes Boulders will spawn on the Arena wall itself, and they will do erratic things like fly out of the Arena. This pulse passes through the And into a cool brick, the Property Controller. This allows you to assign values to Game Properties. Here we have it in "Toggle" mode, so the "leftward" value goes True, False, True, False...

The remaining logic has a symmetric pair of property checks: the "Leftward" Sensor pulses as long as the "leftward" property is True, and the "Rightward" Sensor pulses as long as the "leftward" property is False. These pipe into And controllers and on to their respective Simple Motion Actuators.

Logical. Easy. BGE.

"LifeBar"

This is the active portion of the HUD. Its job is to slide leftward to reveal the "DeathBar" behind it, and then ultimately to end the game when the total health is down to zero. This became the central place for health logic, basically because all three Guys share combined health data. If you wanted to make each Guy die off individually, things would be restructured.

The most basic check is the first row, whose Property Sensor looks to see if the "health" property is below 0. If so, the game ends through the Scene Actuator switching to the "YouLose" scene. (See next section for the short description of the end-game screens.)

The remaining bricks on "LifeBar" receive three types of messages:

  • "Jump" - the speed-bonus for beans being active
  • "Ouch" - colliding with a moving Boulder
  • "Fatigue" - life-drain caused by time

All of these funnel directly into the "lifebar-update.py" Controller, which is the biggest Python script in this .blend. Here it is in two parts so we can discuss the looping and the loop body separately:

import bge
cont = bge.logic.getCurrentController()
own = cont.owner
for sensor in cont.sensors:
    for body in sensor.bodies:

Here you can notice a couple of concepts critical to understanding Brick order-of-execution.

See that the one single invocation of this script is required to handle each of the cont.sensors' pulses, because it loops over each of them here. Also, notice that each Message can have more than one Body. This occurs if the Message-sending object has more than one pulsing Controller connected to a Message Actuator. All of those pulses will merge into a single Message actuation, and each one will be represented by one of the sensor.bodies.

So for the contents of the loop, we have:

        delta = 0.0
        subject = sensor.subject
        if 'Jump' == subject:
            speed = float(body)
            delta = speed * own['speed-bonus-multiplier']
        elif 'Ouch' == subject:
            mass = float(body)
            delta = -mass * own['pain-multiplier']
        elif 'Fatigue' == subject:
            delta = -own['time-deplete']
        else:
            print("Uh-oh, unknown subject: %s" + subject)
        delta *= own['health-delta-scale']
        own['health'] = min(100, own['health'] + delta)
        own.worldPosition.x = own['health']/100 - 1

Each of the three Subjects will affect the health up or down, and that amount is stored in the delta variable. Here we read the sensor.subject and switch based on its value.

The behavior for each of the cases is fairly self-explanatory, but observe that each is scaled by a property of the "LifeBar". In addition, after the if/elif/else statement, there is an overall scale (own['health-delta-scale'] If you play around with adjusting these constants, you will find that it is quite challenging to find a balance between too easy and too difficult.Tutorials-Frijoles-Tweakable.png

The last two notables about this code are that it maxes the health out to 100 by the min() statement, and also that it moves the health bar to the left based on how much health there is. Note that we made the hole of the HUD exactly 1 BU, so you do not have to multiply the "LifeBar" movement by anything to get the right distance.

Blender3D FreeTip.png
API Exploration
Often, especially when starting out with BGE Python, you will have an object and be unsure of what methods it provides. For example, how did we know to call sensor.subject, not sensor.getSubject()? One trick is to add a print(dir(sensor)). This will dump a list of all the attributes of the sensor object to the System Console. Half the time this is all you have to do. The other half of the time you will want to see some more information, so first find out the type of the object by print(type(sensor)), then go to Help » Python API Reference and search for the class there.


"YouLose" and "YouWin" Scenes

These are very simple Scenes, mostly for display purposes. The Text in each has a small row of bricks that senses any key then switches back to the main "Scene". Notice that switching back to this Scene restores it to its original status, with no boulders and full health.

"ClothCatcher"

This one is kind of a nonsense object thrown in to demonstrate the Soft Body. As mentioned elsewhere in this manual, this type is nowhere near as forgiving (fun?) as the Blender-internal cloth simulation. There, you can make changes to most settings and it will usually provide some kind of interesting result. Here, you can make the tiniest change and end up with a non-working result.

To get this "Finish Line Flag" look to work, we had to add a Rigid Body Joint to attach it to the "ClothesPin" object, and we also had to add a non-rendering "ClothCatcher" mesh underneath.

To demonstrate the challenges of BGE Soft Bodies, simply play around with the various objects/options. Most of them will be disappointing.Tutorials-Frijoles-Tweakable.png Applying Rotation and Scale is also very impactive.

El Fin

As you can tell, this is not a very advanced game. You will probably want to create something much more complex. Here are some pages to visit as a next step: