Jump to content

  • Log In with Google      Sign In   
  • Create Account

Avengers UTD Chronicles



BSplines and Typewriter effects

Posted by , in Odyssey UI, The Odyssey 26 August 2014 - - - - - - · 738 views

In the past days, I've been busy working on several things concerning the implementation of cutscenes during the game. In my mind, the game opens on a depiction of the Solar System where the moments just before the Odyssey leaves Earth Orbit would be shown. Imagine yourself as the commander of this ship, in a sci-fi universe of vector graphics and green terminals that beep when keys are typed. Running through the checklist moments before the Odyssey leaves the Solar System you would probably see something like the following image.

Posted Image

This would be a part of the introductory cutscene that I hope to show you in the near future. These screens would be interleaved with "cinematic" title screens explaining the back story. In order to draw the purple path you see in the picture, I needed to implement a BSpline plotting algorithm. It's not yet perfect as I would ideally be able to implement constant arc length parametrization (!). You can't see it from the picture, but the path is being rendered as a 3D polyline joining various rectangle polygons. The issue is that the curve segments are not spaced equidistantly. Then again, if you can't see it then it's not really a problem. It's not high priority right now but I would like to have more control over it.

The second part of the title gets us to the fundamental typewriter effect we so desperatly need in an universe like this. I have implemented this effect using my UI library:

Posted Image

The speed of the Typewriter can be configured using its CharactersPerSecond property. Using this information we can determine how many characters to reveal at each update. The Typewriter is using a Direct2D TextLayout object. Since this object needs to be instantiated with its text content from the beginning, i.e. the text string is an argument of the constructor, I assumed that recreating it at each character update would not be the best approach performance-wise. What I hope is a better solution was to create this object fully invisible (i.e., transparent). The TextLayout allows us to apply brushes (i.e. colours) to specific text ranges. So that's what the animation is doing. At each update it increases the number of character the green brush is applied, while the others stay transparent. Each update generates a TextRangeChanged event, which in turn is used to update the caret's position. The next step will be to implement a blinking animation for the caret and other nice features such as carriage return, line scrolling and so on.

This is a feature that will be used extensively in the actual game. The log window, or more enthusiastically, THE COMM CHANNEL, will display information about what's going on in the minds of the AI-controlled ships. So, THE COMM CHANNEL, will describe "in prose" what's happening on screen. I enjoy both seeing space dogfights but also reading about them. Hopefully, reading sentences like "Red 5, breaking formation", "ALL HANDS, BATTLESTATIONS!", "IT'S A TRAP", or "I'll see you on the other side" will add to the idea of witnessing a space battle from the command room of a capital ship. Ideally the radio chatter would also be fully voiced, but that maybe is stretching too far the imagination considering the resources currently available.

That's it for now, back to implementing the blinking caret Posted Image


Direct2D Glass Buttons and reinventing the wheel

Posted by , in Odyssey UI 11 August 2014 - - - - - - · 1,637 views
Direct2D, GUI library, SharpDX
I am not aware of any GUI library that specifically targets C#, SharpDX and Direct2D so I had to roll my own and well this gave me the opportunity to learn how Direct2D is used. Direct2D is a wonderful API, although its usage is sometimes... hectic to say the least. Although I can understand some of the reasons behind some design choices, you cannot use it as if you were building a WPF XAML based application.

The game I am building is heavily UI-based as any self-respecting strategic game should be. I am an HCI researcher "by trade" so this was the opportunity of a lifetime to put my PhD to good use! I believe that a well-polished game needs an equally polished UI. I needed it to be able to render high-quality controls and also allow me to animate transition between states and so on. I found that Direct2D would not provide me with that kind of features, unless I implemented them. The library, as I am designing it, isn't intended to be a replacement for XAML or as a tool for data-entry applications built with DirectX (why would anyone think of such a thing? Posted Image). Therefore it is being geared towards situation that commonly occur in a 3D game or application. I'm pleased to present you the ubiquitous Glass Button made with Direct2d, SharpDX and the UI library of the Odyssey engine!

Posted Image


The cool thing about this is that I was able to get this working by following a XAML tutorial. How? Enter the... Odyssey eXstensible Interaction Language (I may need a better name..) I designed this language around XAML as it was the one I am most familiar with. The structure of a OXIL theme (!) consists of two parts. At the start of the theme file, Resource elements are declared. Resources are colors and gradients. For example:
<Resources>
    <SolidColor Name="Transparent" Color="#00000000"/>
    <SolidColor Name="Red" Color="#FFFF0000"/>
    <RadialGradient Name="ButtonFillGlow" RadiusX="1" RadiusY="1" Center="0.5, 0.8">
      <GradientStop Color="{Red}" Offset="0"/>
      <GradientStop Color="{Transparent}" Offset="1.0"/>
    </RadialGradient>
</Resources>
Gradients and colors are declared almost identically as the equivalent XAML syntax. The individual gradient stops are written in curly brackets as they reference color resources previously defined. The second part of the document is the actual theme file. Each control defines how it wants to be rendered. Let's take the above glass button as an example:
<ControlStyle Name="Button" Width="200" Height="50" TextDescriptionClass="Button">
    <VisualState>
      <Shapes>
        <Rectangle Name="Background" Position="0,0" StrokeThickness="1" Width="1" Height="1" Fill="{DimGray}" />
        <Rectangle Name="GlowEffect" Position="0,0" Width="1" Height="1" Fill="{ButtonFillGlow}"/>
        <Rectangle Name="Foreground" Position="0,0" Width="1" Height="1" Fill="{ButtonFillForeground}" Stroke="{DarkGray}"/>
        <Rectangle Name="GlassEffect" Position="0,0" Width="1" Height="0.5" Fill="{ButtonFillShine}"/>
      </Shapes>
      <Animations>
        <Animation Name="Highlighted">
          <Color4Curve TargetName="{Foreground}" TargetProperty="Fill.GradientStops[0].Color">
            <Color4KeyFrame Time="0" Value="{DarkBlue}"/>
            <Color4KeyFrame Time="1" Value="{Red}"/>
          </Color4Curve>
        </Animation>
      </Animations>
    </VisualState>
 </ControlStyle>
The ControlStyle element defines the appearance of the control and any animations attached to it, through the VisualState sub-element. In a distant future it might be possible to also specify DataTemplate elements for databinding purposes (another experimental feature supported by the library). The first part of the VisualState element lists the actual drawing instructions to the 2D renderer. Those shapes are drawn top to bottom with the last one being the foremost shape. Differently from XAML, the Position, Width and Height attributes express values relative to the control whose style this will be applied to. The width and height attributes specified at the beginning of the ControlStyle element represent the preferred values to use in absence of changes. In particular, the position attribute specifies to the renderer any offset to use while rendering the shape. A value of (0, 0) means that the shape will begin in the exact location of the host control; a value of (0, 0.5) would mean that the shape would begin in the vertical center of the control and so on. The values associated to the width and height have a similar purpose. As it stands now, it is possible to render shapes that go outside the logical boundary of the control, but they won't be part of hit-testing. The Fill and Stroke attribute reference gradient resources that were previously defined.

The animation elements can be used to define state transition. TargetName defines the Shape whose TargetProperty will be affected. The two keyframes defined successively define the animation to use. In this particular case, the change will be instantaneous. For example:

protected override void OnPointerEnter(PointerEventArgs e)
{
    base.OnPointerEnter(e);
    string animationName = ControlStatus.Highlighted.ToString();
    var animation = AnimationController[animationName];
    animation.Speed = 1.0f;
    AnimationController.Play(animationName);
}

protected override void OnPointerExited(PointerEventArgs e)
{
    base.OnPointerExited(e);
    string animationName = ControlStatus.Highlighted.ToString();
    var animation = AnimationController[animationName];
    animation.Speed = -1.0f;
    AnimationController.Play(animationName);
}
The negative speed value is used to play the animation backward. Though it would be of course possible to specify a completely different one. Under the hood, there is an object walker class that, through reflection, changes the object properties at run time. Right now, only these discrete instantaneous animations are supported But I'm working towards implementing continuous animations. This is not so easy because of how Direct2D handles resources. In Direct2D gradients are really nothing more than textures. When you specify a Direct2D gradient, it creates a texture. Therefore if you wanted to animate the "glow" color of the radial gradient in the picture, you'd have to create as many intermediate gradients as required by the length of your animation.

This is what it does right now for those two discrete keyframes. It creates a different brush as required, if they don't exist already. For two discrete keyframes it wouldn't constitute a big performance hit. However if you wanted to animate it over the course of one second at 60 fps, you'd probably have to create 60 different brushes, which might or might not be a problem. There might be alternate possibilities, such as animating the opacity of different gradients so that it would give the illusion of colours changing. Another possibility might be animating other gradient properties. For example, it should be possible to animate a RadialGradient's RadiusX and RadiusY properties. These could be used to animate a glow effect in a button that grows or pulsates, without recreating the actual resource. I will experiment on these possibilities and report back later this week. Then again, one of the objectives of this library is to give developers the means, then it will be depend on how good your UI design skills are. Which brings us to another topic....

Cool story bro, but how do I use it in my own projects?

You can check out the experimental branch of the Odyssey Engine. That's where I am currently adding these new features. The UI part of the engine can be used without most of the other libraries (which I am using for my own game). You can check out some samples in the MiniUI project. The engine itself is distributed under the terms of the GPLv3 license. Meaning that it can be freely used in academic and other open source projects. It can be used commercially (although it is still far from being commercially viable) as long as the resulting program is open-source as well (I think). These terms are not set in stone and I'm open to discussion. So if you need a UI library, don't reinvent the wheel but help me provide other SharpDX developers a good and comprehensive Gui library. Most of the groundwork is there, it needs more controls and testing. Drop me a line if you'd like to help.





Twitter

August 2014 »

S M T W T F S
     12
3456789
10111213141516
17181920212223
24252627282930
31      


PARTNERS