cc.Sprite.FilterMode.Point nonfunctional & non existant

Like subject says. This filter mode does not work as expected, it seems to have no affect what so ever.

There’s a topic about it here that has gone largely ignored -

What do you need this for?

rendering pixel art at different scales with flat edges -


You should be able to see in the example above, in Unity when using point filter, the sprite retains it’s crisp hard edged look an scales the sprite with nearest neighbor algorithm. Very useful.

I’ve had some mild success with modifying the main.js and a small component that executes on start. Then setting the canvas size to something low like 320x480.

Then you’ll get something that looks like https://persommer.com which maintains the same pixel size disregarding browser height/width.

Component to put on canvas:

const { ccclass, property } = cc._decorator;

@ccclass
export default class CorrectPixelSize extends cc.Component {
  onLoad() {
    let multiplier: number = 0.5;
    cc.view.setDesignResolutionSize(Math.round(cc.view.getCanvasSize().width * multiplier), Math.round(cc.view.getCanvasSize().height * multiplier));
  }
}

Modified start.js.
Notice the

if (cc.sys.os !== cc.sys.OS_ANDROID || cc.sys.browserType !== cc.sys.BROWSER_TYPE_UC) {
          cc.view.enableRetina(false);
          cc.view.enableAntiAlias(false);
        }

And at the very end there is a setRes() for when the browser is resized.

(function () {

  'use strict';

  function boot() {

    var settings = window._CCSettings;
    window._CCSettings = undefined;

    if (!settings.debug) {
      var uuids = settings.uuids;

      var rawAssets = settings.rawAssets;
      var assetTypes = settings.assetTypes;
      var realRawAssets = settings.rawAssets = {};
      for (var mount in rawAssets) {
        var entries = rawAssets[mount];
        var realEntries = realRawAssets[mount] = {};
        for (var id in entries) {
          var entry = entries[id];
          var type = entry[1];
          // retrieve minified raw asset
          if (typeof type === 'number') {
            entry[1] = assetTypes[type];
          }
          // retrieve uuid
          realEntries[uuids[id] || id] = entry;
        }
      }

      var scenes = settings.scenes;
      for (var i = 0; i < scenes.length; ++i) {
        var scene = scenes[i];
        if (typeof scene.uuid === 'number') {
          scene.uuid = uuids[scene.uuid];
        }
      }

      var packedAssets = settings.packedAssets;
      for (var packId in packedAssets) {
        var packedIds = packedAssets[packId];
        for (var j = 0; j < packedIds.length; ++j) {
          if (typeof packedIds[j] === 'number') {
            packedIds[j] = uuids[packedIds[j]];
          }
        }
      }
    }

    // init engine
    var canvas;

    if (cc.sys.isBrowser) {
      canvas = document.getElementById('GameCanvas');
    }

    function setLoadingDisplay() {
      // Loading splash scene
      var splash = document.getElementById('splash');
      var progressBar = splash.querySelector('.progress-bar span');
      cc.loader.onProgress = function (completedCount, totalCount, item) {
        var percent = 100 * completedCount / totalCount;
        if (progressBar) {
          progressBar.style.width = percent.toFixed(2) + '%';
        }
      };
      splash.style.display = 'block';
      progressBar.style.width = '0%';

      cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function () {
        splash.style.display = 'none';
      });
    }

    var onStart = function () {
      cc.view.resizeWithBrowserSize(true);

      if (!false) {
        // UC browser on many android devices have performance issue with retina display
        if (cc.sys.os !== cc.sys.OS_ANDROID || cc.sys.browserType !== cc.sys.BROWSER_TYPE_UC) {
          cc.view.enableRetina(false);
          cc.view.enableAntiAlias(false);
        }
        if (cc.sys.isBrowser) {
          setLoadingDisplay();
        }

        if (cc.sys.isMobile) {
          if (settings.orientation === 'landscape') {
            cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);
          } else if (settings.orientation === 'portrait') {
            cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);
          }
          // qq, wechat, baidu
          cc.view.enableAutoFullScreen(
            cc.sys.browserType !== cc.sys.BROWSER_TYPE_BAIDU &&
            cc.sys.browserType !== cc.sys.BROWSER_TYPE_WECHAT &&
            cc.sys.browserType !== cc.sys.BROWSER_TYPE_MOBILE_QQ
          );
        }

        // Limit downloading max concurrent task to 2,
        // more tasks simultaneously may cause performance draw back on some android system / brwosers.
        // You can adjust the number based on your own test result, you have to set it before any loading process to take effect.
        if (cc.sys.isBrowser && cc.sys.os === cc.sys.OS_ANDROID) {
          cc.macro.DOWNLOAD_MAX_CONCURRENT = 2;
        }
      }

      // init assets
      cc.AssetLibrary.init({
        libraryPath: 'res/import',
        rawAssetsBase: 'res/raw-',
        rawAssets: settings.rawAssets,
        packedAssets: settings.packedAssets,
        md5AssetsMap: settings.md5AssetsMap
      });

      var launchScene = settings.launchScene;

      // load scene
      cc.director.loadScene(launchScene, null,
        function () {
          if (cc.sys.isBrowser) {
            // show canvas
            canvas.style.visibility = '';
            var div = document.getElementById('GameDiv');
            if (div) {
              div.style.backgroundImage = '';
            }
          }
          cc.loader.onProgress = null;
          console.log('Success to load scene: ' + launchScene);
          setRes();
        }
      );
      setRes();
    };

    // jsList
    var jsList = settings.jsList;
    var bundledScript = settings.debug ? 'src/project.dev.js' : 'src/project.js';
    if (jsList) {
      jsList = jsList.map(function (x) {
        return 'src/' + x;
      });
      jsList.push(bundledScript);
    } else {
      jsList = [bundledScript];
    }

    // anysdk scripts
    if (cc.sys.isNative && cc.sys.isMobile) {
      jsList = jsList.concat(['src/anysdk/jsb_anysdk.js', 'src/anysdk/jsb_anysdk_constants.js']);
    }


    var option = {
      //width: width,
      //height: height,
      id: 'GameCanvas',
      scenes: settings.scenes,
      debugMode: settings.debug ? cc.DebugMode.INFO : cc.DebugMode.ERROR,
      showFPS: !false && settings.debug,
      frameRate: 60,
      jsList: jsList,
      groupList: settings.groupList,
      collisionMatrix: settings.collisionMatrix,
      renderMode: 0
    };

    cc.game.run(option, onStart);

    // start process
    optimizedResize.add(function () {
      setRes();
    });


  }

  if (window.jsb) {
    require('src/settings.js');
    require('src/jsb_polyfill.js');
    boot();
  } else if (false) {
    require(window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js');
    var prevPipe = cc.loader.md5Pipe || cc.loader.assetLoader;
    cc.loader.insertPipeAfter(prevPipe, wxDownloader);
    boot();
  } else if (window.document) {
    var splash = document.getElementById('splash');
    splash.style.display = 'block';

    var cocos2d = document.createElement('script');
    cocos2d.async = true;
    cocos2d.src = window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js';

    var engineLoaded = function () {
      document.body.removeChild(cocos2d);
      cocos2d.removeEventListener('load', engineLoaded, false);
      window.eruda && eruda.init() && eruda.get('console').config.set('displayUnenumerable', false);
      boot();
    };
    cocos2d.addEventListener('load', engineLoaded, false);
    document.body.appendChild(cocos2d);
  }

})();





function setRes() {
  var multiplier = 0.5;

  // if (cc.view.getDevicePixelRatio() >= 2) {
  //   multiplier = 1;
  // }
  // multiplier = 1;
  cc.view.setDesignResolutionSize(Math.round(cc.view.getCanvasSize().width * multiplier), Math.round(cc.view.getCanvasSize().height * multiplier));
  //console.log('setRes', cc.view.getDesignResolutionSize());
}

(function () {
  var throttle = function (type, name, obj) {
    obj = obj || window;
    var running = false;
    var func = function () {
      if (running) {
        return;
      }
      running = true;
      requestAnimationFrame(function () {
        obj.dispatchEvent(new CustomEvent(name));
        running = false;
      });
    };
    obj.addEventListener(type, func);
  };

  /* init - you can init any event */
  throttle("resize", "optimizedResize");
})();

var optimizedResize = (function () {

  var callbacks = [],
    running = false;

  // fired on resize event
  function resize() {

    if (!running) {
      running = true;

      if (window.requestAnimationFrame) {
        window.requestAnimationFrame(runCallbacks);
      } else {
        setTimeout(runCallbacks, 66);
      }
    }

  }

  // run the actual callbacks
  function runCallbacks() {

    callbacks.forEach(function (callback) {
      callback();
    });

    running = false;
  }

  // adds callback to loop
  function addCallback(callback) {

    if (callback) {
      callbacks.push(callback);
    }

  }

  return {
    // public method to add additional callback
    add: function (callback) {
      if (!callbacks.length) {
        window.addEventListener('resize', resize);
      }
      addCallback(callback);
    }
  }
}());

That’s interesting, is there a reason why you put it in main instead of setting up a listener for resize and running the setDesignResolutionSize function in the project?

I would also still prefer the GUI would handle this and support for sprite filter mode was implemented, as downsampling the whole view effects everything in the view, not just individual sprites. Imagine a game where the character grows in size but you want to retain the crisp edges of everything while not scaling anything else.

It’s been a while since I worked on that but if I remember correctly it was because there was a small delay before the code would execute and the screen would look wrong.

The reason I made it with a small screen size is because I don’t like it when objects have mixed pixel sizes.

@slackmoehrle could i bother you and the team to weigh in on this?

Of course. I will ping them. Please realize this is their weekend so I may not get a response for a few days.

1 Like

thanks, no worries

hi,@skara, the reason is that texture is raw asset in current versions, so we could not serialize the filter options on that. We will modify it to asset in v2.0

The simplest way currently is to get the texture object and use: tex.setAliasTexParameters()

2 Likes