Small program DOM based small town game development

Posted by summerpewp on Sun, 06 Feb 2022 22:00:06 +0100

1. Background

A small town game needs to be developed, including the functions of building, upgrading buildings, building, producing gold coins, collecting gold coins and so on. The overall complexity is not too high. It is mainly the circular animation of buildings and the animation of small elements such as cars and windmills on the map. Therefore, DOM+CSS3 animation is considered.

2. Development problem solving

2.1 hierarchical control

The stage of the town is to use the movable area and movable view components provided by the applet to realize the effect of mobile map. At first, in order to facilitate the elements in the map to move together, the background map and the building layer were placed in a movable view. The implementation code is as follows:

<movable-area>
    <movable-view>
        <!-- Background layer -->
        <view class="map-layer"></view>
        <!-- Mantle -->
        <view class="mask-layer"></view>
        <!-- Building floor -->
        <view class="building-layer">
            <!-- Architecture -->
        </view>
    </movable-view>
    <!-- Button layer -->
    <view class="button-layer"></view>
</movable-area>

However, in fact, when entering the architectural planning state, the required effect is that the map layer is under the mask layer and the building layer is on the mask layer. At the same time, some operation buttons are under the mask layer and the planning "exit" button is above the mask layer (as shown in the figure below).

If the above code is implemented and the mask is placed in the movable view, the mask is affected by the hierarchy of the movable view and cannot be flexibly adjusted to cover only some buttons. Therefore, the mask must be moved outside the movable view, and only one element layer can be reserved in the movable view.

In order to facilitate the control of hierarchy, the map background layer and mask layer are at the same level as movable view, so that the hierarchy of mask layer can be flexibly adjusted through the control of z-index. The hierarchy adjustment code is as follows:

<movable-area>
    <!-- Background layer -->
    <view class="map-layer"></view>
    <!-- Mantle -->
    <view class="mask-layer"></view>
    <movable-view>
        <!-- Building floor -->
        <view class="building-layer">
            <!-- Architecture -->
        </view>
    </movable-view>
    <!-- Button layer -->
    <view class="button-layer"></view>
</movable-area>

However, when the background layer is separated from the movable view, it cannot be dragged and moved. At this time, you can use wxs response event By binding the change event to the movable view, when the movable view is moved, the coordinates of the background layer are changed at the same time to make the background layer follow the movement. Add event binding code as follows:

// moveEvent.wxs
const moveHandler = (e, ownIns) => {
  const map = ownIns.selectComponent('.map-layer');
  map.setStyle({
    left: `${e.detail.x}px`,
    top: `${e.detail.y}px`,
  });
};

module.exports = {
  moveHandler,
}
<wxs module="event" src="moveEvent.wxs" />
<movable-area>
    <!-- Background layer -->
    <view class="map-layer" style="position:absolute;left:{{x}}px;top:{{y}}px;"></view>
    <!-- Mantle -->
    <view class="mask-layer"></view>
    <movable-view x="{{x}}" y="{{y}}" bindchange="{{event.moveHandler}}">
        <!-- Building floor -->
        <view class="building-layer">
            <!-- Architecture -->
        </view>
    </movable-view>
    <!-- Button layer -->
    <view class="button-layer"></view>
</movable-area>

There is no problem debugging on developer tools by changing the methods of left and top, but try adjusting it on the real iphone. After dragging a few times, the interface flashes, and then the applet flashes back.

Analyze the reasons. Changing the position by setting left and top will cause frequent rearrangement. In the environment of applet, frequent trigger rearrangement will lead to the flash back of applet. Therefore, it is necessary to use the displacement attribute that does not cause rearrangement for position control, that is, the transform attribute in css3. Controlling the mobile GPU process through the translate value of transform will open a new composite layer for it and will not affect the default composite layer (i.e. ordinary file stream). The modified code is as follows:

// moveEvent.wxs
const moveHandler = (e, ownIns) => {
  const map = ownIns.selectComponent('.map-layer');
  map.setStyle({
    transform: `translate3d(${e.detail.x}px, ${e.detail.y}px, 0)`,
  });
};

module.exports = {
  moveHandler,
};
<wxs module="event" src="moveEvent.wxs" />
<movable-area>
    <!-- Background layer -->
    <view class="map-layer" style="transform:translate3d({{x}}px,{{y}}px,0);"></view>
    <!-- Mantle -->
    <view class="mask-layer"></view>
    <movable-view x="{{x}}" y="{{y}}" bindchange="{{event.moveHandler}}">
        <!-- Building floor -->
        <view class="building-layer">
            <!-- Architecture -->
        </view>
    </movable-view>
    <!-- Button layer -->
    <view class="button-layer"></view>
</movable-area>

So far, the hierarchical control of the mask layer has been solved, and the background layer and the building layer contained in the movable view can also move smoothly.

2.2 animation implementation problems

2.2.1 frame by frame animation jitter

When the mobile terminal adapts, the web terminal uses the rem unit and the applet terminal uses the rpx unit. When the applet realizes frame by frame animation, rpx is used as the unit. Under the screen with non-standard 375 width, the display of frame by frame animation may shake due to the problem of calculation accuracy (as shown in the figure below).

In order to avoid the problem of calculation accuracy, we use px as the unit in the style of frame by frame animation, and then scale it according to the corresponding proportion of the current device screen width, so as to achieve the effect of animation stability. The code example is as follows

const { screenWidth } = wx.getSystemInfoSync();
Page({
    data: {
        scaleVal: 0.5 * (screenWidth / 375),
    }
})
<movable-view x="{{x}}" y="{{y}}" bindchange="{{event.moveHandler}}">
    <!-- Building floor -->
    <view class="building-layer" style="transform:scale({{scaleVal}});transform-origin:0 0 0;">
        <!-- Building (Interior) CSS use px (as unit) -->
    </view>
</movable-view>    

Using px as the unit and scaling the parent container perfectly solves the problem of frame by frame animation jitter.

2.2.2 variable animation

In the current version, there are five buildings in the town. Each building has three frame by frame animation states: under construction, under operation and under destruction. Examples are as follows:

Each building has 10 levels, and a total of 150 sets of animation styles need to be written. If it is written according to the traditional animation implementation method, the css implementation code of a set of animation is as follows:

/* Animation in business */
.mall-lv10-open {
  display: block;
  width: 100%;
  height: 100%;
  animation: mall-lv10-open-animation 1s steps(24) infinite;
  background-size: 10100rpx 314rpx;
  background-image: url(https://680acd229c2c4c6db1594628946e6899.png);
}

@keyframes mall-lv10-open-animation {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: -9696px 0;
  }
}

If you write 150 sets of such code according to the above method, the amount of code is quite large. If there are modifications in the middle, it will be particularly troublesome to find; In addition, it is hoped that the shape and animation of the building can be distributed to the front-end display through the interface after being configured through the background system. The form of hardcode like the above cannot realize flexible configuration.

The general css style can be generated and injected by js through the style on the label. However, the animation @ keyframes attribute of css3 cannot be used in the style on the line, but can only be used in css files or < style > labels. The applet cannot inject style or code dynamically, so the only problem we need to solve is how to set @ keyframes dynamically in the applet.

Through analysis, it is found that the @ keyframes of all architectural animation are basically the same. The only change is the width of the background image. In frame by frame animation, it is the displacement background position of the background image. As long as the value of background position in @ keyframes can be changed dynamically. What we use here is css variable The way.

First, configure json for different levels and states of each building:

{
  "mall": {
    "lv1": {
      "width": 364,
      "height": 249,
      "init": {
        "url": "https://250e112b90e641c7bd46edae65ecf671.png",
        "frame": 11,
        "fillMode": "forwards"
      },
      "working": {
        "url": "https://bb371286db4d434180b0874172bb9a6a.png",
        "frame": 19,
        "fillMode": "infinite"
      },
      "destory": {
        "url": "https://78ba80096c8940c0b106ebb1bcc09075.png",
        "frame": 11,
        "fillMode": "forwards"
      }
    },
    ...
  },
  ...
}

Then js generates css style by parsing json. The generated style contains -- bgWidth, which is passed into the style of inline style as css variable:

export const getBuildingAnimationStyle = ({ url, width, height, frame, fillMode, duration }) => {
  const step = frame - 1;
  return [
    `--bgWidth:-${width * step}rpx;`,
    `animation:building-animation ${duration || (frame / 10)}s steps(${step}) ${fillMode || 'infinite'};`,
    `background-size:${width * frame}rpx ${height}px;`,
    `background-image:url(${url});`,
  ].join('');
};

The css part uses the passed in dynamic variables through var(--bgWidth):

@keyframes building-animation {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: var(--bgWidth) 0;
  }
}

The function of variable animation can be easily realized through css variables, saving thousands of lines of code.

There is also a small pit here. When the animation generated through js is transferred to the in-line style, if you want to dynamically switch the animation (for example, the construction of Huiju town is to switch from the destruction animation to the construction animation, and then to the running animation), you must first empty the overall style attribute once, and then set the animation, otherwise the animation may be disordered.

3. Conclusion

This article is just to share some tips to solve problems in the process of developing small town games, as well as some follow-up supplements related to the realization of gold coin motion animation and performance optimization.

Topics: Mini Program