();
25 | data['id'] = this.id;
26 | data['name'] = this.name;
27 | data['type'] = this.type;
28 | data['visible'] = this.visible;
29 | data['x'] = this.x;
30 | data['y'] = this.y;
31 | return data;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/game/df_game_loop.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/scheduler.dart';
2 |
3 | /// 游戏循环
4 | class DFGameLoop {
5 | Duration previous = Duration.zero;
6 | late Ticker _ticker;
7 | void Function(double dt) update;
8 |
9 | /// 创建游戏循环
10 | DFGameLoop(this.update) {
11 | _ticker = Ticker(_tick);
12 | }
13 |
14 | void _tick(Duration timestamp) {
15 | final dt = _computeDelta(timestamp);
16 | update(dt);
17 | }
18 |
19 | double _computeDelta(Duration now) {
20 | final delta = previous == Duration.zero ? Duration.zero : now - previous;
21 | previous = now;
22 | return delta.inMicroseconds / Duration.microsecondsPerSecond;
23 | }
24 |
25 | void start() {
26 | _ticker.start();
27 | }
28 |
29 | void stop() {
30 | _ticker.stop();
31 | }
32 |
33 | void dispose() {
34 | _ticker.dispose();
35 | }
36 |
37 | void pause() {
38 | _ticker.muted = true;
39 | previous = Duration.zero;
40 | }
41 |
42 | void resume() {
43 | _ticker.muted = false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DevilF Engine
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | A Flutter 2D RPG Game Engine On Web & Android & IOS.
14 |
15 |
16 |
17 | The Devilf Engine Is A Open Source 2D Game Engine.
18 | The Engine Is Development Using Flutter & Dart Language.
19 | You Can Use It Independently In Your Flutter Project.
20 | Some Documentation Of How To Use It Can Be Found Here.
21 |
22 |
23 | ## Home
24 | https://devilf.com
25 |
26 | ## Docs
27 | https://ymbok.com/book/devilf.html
28 |
29 | ## Example
30 | https://github.com/ym6745476/devilf
31 |
32 | ## Online Demo
33 | https://ymbok.com/download/slayer.html
34 |
35 | ## Install
36 | ```yaml
37 | dependencies:
38 | devilf: ^0.1.0
39 | ```
40 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/lib/building/stone_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 | import 'package:devilf_engine/core/df_position.dart';
4 | import 'package:devilf_engine/core/df_size.dart';
5 | import 'package:devilf_engine/sprite/df_sprite.dart';
6 | import 'package:flutter/cupertino.dart';
7 |
8 | /// 石头精灵类
9 | class StoneSprite extends DFSprite {
10 | StoneSprite({DFSize size = const DFSize(32, 32)}) : super(position: DFPosition(0, 0), size: size);
11 |
12 | @override
13 | void update(double dt) {
14 | this.angle += dt * 0.25;
15 | }
16 |
17 | @override
18 | void render(Canvas canvas) {
19 | canvas.save();
20 |
21 | /// 将子精灵转换为相对坐标
22 | if (parent == null) {
23 | canvas.translate(position.x, position.y);
24 | } else {
25 | canvas.translate(position.x - parent!.size.width / 2, position.y - parent!.size.height / 2);
26 | }
27 |
28 | canvas.rotate(pi * 2 * this.angle);
29 | var paint = new Paint()..color = new Color(0xFFFF0000);
30 | canvas.drawRect(new Rect.fromLTWH(-size.width / 2, -size.height / 2, size.width, size.height), paint);
31 | canvas.restore();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/lib/core/df_position.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | /// 位置
4 | class DFPosition {
5 | /// x坐标
6 | double x;
7 |
8 | /// y坐标
9 | double y;
10 |
11 | /// 创建坐标
12 | DFPosition(this.x, this.y);
13 |
14 | /// 转换为Offset
15 | Offset toOffset() => Offset(x, y);
16 |
17 | @override
18 | bool operator == (Object other) {
19 | if (other is DFPosition) {
20 | return x == other.x && y == other.y;
21 | }
22 | return false;
23 | }
24 |
25 | /// 转换为字符串
26 | @override
27 | String toString() {
28 | return "x:" + x.toString() + ",y:" + y.toString();
29 | }
30 | }
31 |
32 | /// 瓦片位置
33 | class DFTilePosition {
34 | int x;
35 | int y;
36 |
37 | DFTilePosition(this.x, this.y);
38 |
39 | @override
40 | bool operator ==(Object other) {
41 | if (other is DFTilePosition) {
42 | return x == other.x && y == other.y;
43 | }
44 | return false;
45 | }
46 |
47 | @override
48 | String toString() {
49 | return "x:" + x.toString() + ",y:" + y.toString();
50 | }
51 | }
52 |
53 | /// 方位
54 | enum DFGravity {
55 | /// 左
56 | left,
57 |
58 | /// 上
59 | top,
60 |
61 | /// 右
62 | right,
63 |
64 | /// 下
65 | bottom,
66 |
67 | /// 中
68 | center
69 | }
70 |
--------------------------------------------------------------------------------
/lib/tiled/df_tile_layer.dart:
--------------------------------------------------------------------------------
1 | import 'df_map_layer.dart';
2 |
3 | /// TileLayer节点
4 | class DFTileLayer extends DFMapLayer {
5 | List? data;
6 | double? height;
7 | double? width;
8 |
9 | DFTileLayer({
10 | this.data,
11 | this.height,
12 | this.width,
13 | });
14 |
15 | DFTileLayer.fromJson(Map json) {
16 | data = json['data'].cast();
17 | height = double.tryParse(json['height'].toString()) ?? 0.0;
18 | id = json['id'];
19 | name = json['name'];
20 | opacity = double.tryParse(json['opacity'].toString()) ?? 0.0;
21 | type = json['type'].toString();
22 | visible = json['visible'];
23 | width = double.tryParse(json['width'].toString()) ?? 0.0;
24 | x = double.tryParse(json['x'].toString()) ?? 0.0;
25 | y = double.tryParse(json['y'].toString()) ?? 0.0;
26 | }
27 |
28 | Map toJson() {
29 | final Map data = new Map();
30 | data['data'] = this.data;
31 | data['height'] = this.height;
32 | data['id'] = this.id;
33 | data['name'] = this.name;
34 | data['opacity'] = this.opacity;
35 | data['type'] = this.type;
36 | data['visible'] = this.visible;
37 | data['width'] = this.width;
38 | data['x'] = this.x;
39 | data['y'] = this.y;
40 | return data;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.1.0] - 2021/09/26.
2 | * Update Flutter 2.5.0
3 | * Optimize
4 | * GameWidget Support GestureDetector
5 | * Update GameRenderBox as SpriteRenderBox
6 | * New SpriteWidget Class
7 |
8 | ## [0.0.9] - 2021/08/19.
9 | * Optimize
10 | * New CheckButton Widget
11 | * New UiUtil ShowLayer Method
12 |
13 | ## [0.0.8] - 2021/08/15.
14 | * Optimize
15 |
16 | ## [0.0.7] - 2021/08/10.
17 | * New A * Best Path Algorithm
18 | * New Collision Shape And Collision Function
19 | * New Draw Alpha Layer And Block layer
20 |
21 | ## [0.0.6] - 2021/08/01.
22 | * New Button Widget
23 | * New Map Tile Dynamic Load
24 | * New Camera, And Follows The Character
25 | * New TileMapSprite Support Tiled Json File Format
26 | * Optimize JoystickWidget Not Fixed Position
27 | * New ProgressSprite
28 |
29 | ## [0.0.5] - 2021/07/16.
30 | * Optimize
31 |
32 | ## [0.0.4] - 2021/07/15.
33 | * New JoystickWidget
34 |
35 | ## [0.0.3] - 2021/07/11.
36 | * New Sprite Rendering
37 | * Rename All Classes Add DF Prefix
38 | * New TexturePacker Plist->Json File Format
39 | * New AnimationSprite
40 | * New ImageSprite
41 | * New TextSprite
42 |
43 | ## [0.0.2] - 2021/07/07.
44 | * New GameScene
45 | * New AssetsLoader
46 | * New GameRenderBox
47 |
48 | ## [0.0.1] - 2021/07/06.
49 | * New GameLoop
50 | * New GameWidget
51 | * New Sprite
--------------------------------------------------------------------------------
/lib/game/df_assets_loader.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 | import 'dart:typed_data';
4 | import 'dart:ui' as ui;
5 | import 'package:flutter/services.dart' show rootBundle;
6 |
7 | /// 资源加载和缓存管理
8 | class DFAssetsLoader {
9 | /// 缓存管理
10 | static final Map _files = {};
11 |
12 | DFAssetsLoader();
13 |
14 | /// 从Cache删除文件
15 | static void clear(String file) {
16 | _files.remove(file);
17 | }
18 |
19 | /// 清空缓存
20 | static void clearCache() {
21 | _files.clear();
22 | }
23 |
24 | /// 读取图片
25 | static Future loadImage(String src) async {
26 | if (!_files.containsKey(src)) {
27 | final data = await rootBundle.load(src);
28 | final bytes = Uint8List.view(data.buffer);
29 | final completer = Completer();
30 | ui.decodeImageFromList(bytes, completer.complete);
31 | _files[src] = completer.future;
32 | }
33 | return _files[src] as Future;
34 | }
35 |
36 | /// 读取文本文件
37 | static Future loadText(String src) async {
38 | if (!_files.containsKey(src)) {
39 | _files[src] = await rootBundle.loadString(src);
40 | }
41 | return _files[src] as String;
42 | }
43 |
44 | /// 读取Json文件
45 | static Future> loadJson(String src) async {
46 | final content = await rootBundle.loadString(src);
47 | return jsonDecode(content) as Map;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/tiled/df_object_group.dart:
--------------------------------------------------------------------------------
1 | import 'df_map_layer.dart';
2 | import 'df_objects.dart';
3 |
4 | /// ObjectGroup节点
5 | class DFObjectGroup extends DFMapLayer {
6 | String? drawOrder;
7 | List? objects;
8 |
9 | DFObjectGroup({
10 | this.drawOrder,
11 | this.objects,
12 | });
13 |
14 | DFObjectGroup.fromJson(Map json) {
15 | drawOrder = json['draworder'];
16 | id = json['id'];
17 | name = json['name'];
18 | if (json['objects'] != null) {
19 | objects = [];
20 | json['objects'].forEach((v) {
21 | objects?.add(new DFObjects.fromJson(v));
22 | });
23 | }
24 | opacity = double.parse(json['opacity'].toString());
25 | type = json['type'].toString();
26 | visible = json['visible'];
27 | x = double.tryParse(json['x'].toString()) ?? 0.0;
28 | y = double.tryParse(json['y'].toString()) ?? 0.0;
29 | }
30 |
31 | Map toJson() {
32 | final Map data = new Map();
33 | data['draworder'] = this.drawOrder;
34 | data['id'] = this.id;
35 | data['name'] = this.name;
36 | if (this.objects != null) {
37 | data['objects'] = this.objects?.map((v) => v.toJson()).toList();
38 | }
39 | data['opacity'] = this.opacity;
40 | data['type'] = this.type;
41 | data['visible'] = this.visible;
42 | data['x'] = this.x;
43 | data['y'] = this.y;
44 | return data;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, zhaoqipeng
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/lib/tiled/df_tile_set.dart:
--------------------------------------------------------------------------------
1 | import 'df_tile.dart';
2 |
3 | /// TileSet节点
4 | class DFTileSet {
5 | int? id;
6 | int? columns;
7 | int? firsTgId;
8 | String? image;
9 | int? imageHeight;
10 | int? imageWidth;
11 | int? margin;
12 | String? name;
13 | int? spacing;
14 | int? tileCount;
15 | int? tileHeight;
16 | int? tileWidth;
17 | List? tiles;
18 |
19 | DFTileSet({this.firsTgId});
20 |
21 | DFTileSet.fromJson(Map json) {
22 | firsTgId = json['firstgid'];
23 | columns = json['columns'];
24 | image = json['image'];
25 | imageHeight = json['imageheight'];
26 | imageWidth = json['imagewidth'];
27 | margin = json['margin'];
28 | name = json['name'];
29 | spacing = json['spacing'];
30 | tileCount = json['tilecount'];
31 | tileHeight = json['tileheight'];
32 | tileWidth = json['tilewidth'];
33 | if(json['tiles']!=null){
34 | tiles = List.generate(json['tiles'].length, (index) => DFTile.fromJson(json['tiles'][index]));
35 | }
36 | }
37 |
38 | Map toJson() {
39 | final Map data = new Map();
40 | data['firsTgId'] = this.firsTgId;
41 | data['columns'] = this.columns;
42 | data['image'] = this.image;
43 | data['imageHeight'] = this.imageHeight;
44 | data['imageWidth'] = this.imageWidth;
45 | data['margin'] = this.margin;
46 | data['name'] = this.name;
47 | data['spacing'] = this.spacing;
48 | data['tileCount'] = this.tileCount;
49 | data['tileHeight'] = this.tileHeight;
50 | data['tileWidth'] = this.tileWidth;
51 | data['tiles'] = this.tiles;
52 | return data;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | example
30 |
31 |
32 |
33 |
36 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: devilf_engine
2 | description: A Flutter 2D RPG Game Engine On Web & Android & IOS.
3 | version: 0.1.0
4 | homepage: https://github.com/ym6745476/devilf_engine
5 |
6 | environment:
7 | sdk: ">=2.12.0 <3.0.0"
8 | flutter: ">=1.17.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | audioplayers: ^0.20.1
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 |
19 | # For information on the generic Dart part of this file, see the
20 | # following page: https://dart.dev/tools/pub/pubspec
21 |
22 | # The following section is specific to Flutter.
23 | flutter:
24 |
25 | # To add assets to your package, add an assets section, like this:
26 | # assets:
27 | # - images/a_dot_burr.jpeg
28 | # - images/a_dot_ham.jpeg
29 | #
30 | # For details regarding assets in packages, see
31 | # https://flutter.dev/assets-and-images/#from-packages
32 | #
33 | # An image asset can refer to one or more resolution-specific "variants", see
34 | # https://flutter.dev/assets-and-images/#resolution-aware.
35 |
36 | # To add custom fonts to your package, add a fonts section here,
37 | # in this "flutter" section. Each entry in this list should have a
38 | # "family" key with the font family name, and a "fonts" key with a
39 | # list giving the asset and other descriptors for the font. For
40 | # example:
41 | # fonts:
42 | # - family: Schyler
43 | # fonts:
44 | # - asset: fonts/Schyler-Regular.ttf
45 | # - asset: fonts/Schyler-Italic.ttf
46 | # style: italic
47 | # - family: Trajan Pro
48 | # fonts:
49 | # - asset: fonts/TrajanPro.ttf
50 | # - asset: fonts/TrajanPro_Bold.ttf
51 | # weight: 700
52 | #
53 | # For details regarding fonts in packages, see
54 | # https://flutter.dev/custom-fonts/#from-packages
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Flutter.podspec
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/ephemeral
64 | **/ios/Flutter/app.flx
65 | **/ios/Flutter/app.zip
66 | **/ios/Flutter/flutter_assets/
67 | **/ios/Flutter/flutter_export_environment.sh
68 | **/ios/ServiceDefinitions.json
69 | **/ios/Runner/GeneratedPluginRegistrant.*
70 |
71 | # Exceptions to above rules.
72 | !**/ios/**/default.mode1v3
73 | !**/ios/**/default.mode2v3
74 | !**/ios/**/default.pbxuser
75 | !**/ios/**/default.perspectivev3
76 |
--------------------------------------------------------------------------------
/lib/tiled/df_group_layer.dart:
--------------------------------------------------------------------------------
1 | import 'df_image_layer.dart';
2 | import 'df_layer_type.dart';
3 | import 'df_map_layer.dart';
4 | import 'df_object_group.dart';
5 | import 'df_tile_layer.dart';
6 |
7 | /// GroupLayer节点
8 | class DFGroupLayer extends DFMapLayer {
9 | List? layers;
10 |
11 | DFGroupLayer.fromJson(Map json) {
12 | id = json['id'];
13 | name = json['name'];
14 | type = json['type'].toString();
15 | visible = json['visible'];
16 | opacity = double.tryParse(json['opacity'].toString()) ?? 0.0;
17 | x = double.tryParse(json['x'].toString()) ?? 0.0;
18 | y = double.tryParse(json['y'].toString()) ?? 0.0;
19 |
20 | if (json['layers'] != null) {
21 | layers = [];
22 | json['layers'].forEach((v) {
23 | if (v['type'] == DFLayerType.tileLayer) {
24 | layers?.add(DFTileLayer.fromJson(v));
25 | } else if (v['type'] == DFLayerType.objectGroup) {
26 | layers?.add(DFObjectGroup.fromJson(v));
27 | } else if (v['type'] == DFLayerType.imageLayer) {
28 | layers?.add(DFImageLayer.fromJson(v));
29 | } else if (v['type'] == DFLayerType.group) {
30 | layers?.add(DFGroupLayer.fromJson(v));
31 | } else {
32 | layers?.add(DFMapLayer.fromJson(v));
33 | }
34 | });
35 | }
36 | }
37 |
38 | Map toJson() {
39 | final Map data = new Map();
40 | data['id'] = this.id;
41 | data['name'] = this.name;
42 | if (this.layers != null) {
43 | data['layers'] = this.layers?.map((v) => v.toJson()).toList();
44 | }
45 | data['opacity'] = this.opacity;
46 | data['type'] = this.type;
47 | data['visible'] = this.visible;
48 | data['x'] = this.x;
49 | data['y'] = this.y;
50 | return data;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/sprite/df_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'package:devilf_engine/core/df_rect.dart';
3 | import 'package:devilf_engine/core/df_shape.dart';
4 | import 'package:flutter/cupertino.dart';
5 | import 'package:devilf_engine/core/df_position.dart';
6 | import 'package:devilf_engine/core/df_size.dart';
7 |
8 | /// 基础精灵类
9 | class DFSprite {
10 | /// 唯一标识
11 | String? key;
12 |
13 | /// 相对坐标
14 | DFSprite? parent;
15 |
16 | /// 坐标 左上角是0点
17 | DFPosition position;
18 |
19 | /// 尺寸
20 | DFSize size;
21 |
22 | /// 角度
23 | double angle = 0;
24 |
25 | /// 缩放比例
26 | double scale = 1;
27 |
28 | /// 子精灵
29 | List children = [];
30 |
31 | /// 固定到屏幕
32 | bool fixed = false;
33 |
34 | /// 显示状态
35 | bool visible = true;
36 |
37 | /// 是否回收
38 | bool recyclable = false;
39 |
40 | /// 创建精灵
41 | DFSprite({required this.position, required this.size});
42 |
43 | /// 增加子精灵
44 | void addChild(DFSprite sprite) {
45 | /// 绑定父和子的关系
46 | sprite.parent = this;
47 | children.add(sprite);
48 | }
49 |
50 | /// 移除子精灵
51 | void removeChild(DFSprite sprite) {
52 | sprite.parent = null;
53 | sprite.visible = false;
54 | sprite.recyclable = true;
55 | }
56 |
57 | /// 增加精灵 增加进来精灵才能被绘制
58 | void addChildren(List sprites) {
59 | sprites.forEach((sprite) {
60 | /// 绑定父和子的关系
61 | sprite.parent = this;
62 | });
63 | children.addAll(sprites);
64 | }
65 |
66 | /// 碰撞形状
67 | DFShape getCollisionShape() {
68 | return DFRect(this.position.x - this.size.width / 2, this.position.y - this.size.height / 2, this.size.width,
69 | this.size.height);
70 | }
71 |
72 | /// 精灵更新
73 | void update(double dt) {
74 | /// 子类覆盖
75 | /// 清除不可见的并且需要回收的的精灵
76 | children.removeWhere((sprite) => (sprite.visible == false && sprite.recyclable));
77 | }
78 |
79 | /// 精灵渲染
80 | void render(Canvas canvas) {
81 | /// 子类覆盖
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/core/df_rect.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'df_circle.dart';
3 | import 'df_position.dart';
4 | import 'df_shape.dart';
5 |
6 | /// 矩形
7 | class DFRect extends DFShape {
8 | /// 左坐标
9 | double left = 0;
10 |
11 | /// 上坐标
12 | double top = 0;
13 |
14 | /// 右坐标
15 | double right = 0;
16 |
17 | /// 下坐标
18 | double bottom = 0;
19 |
20 | /// 宽度
21 | final double width;
22 |
23 | /// 高度
24 | final double height;
25 |
26 | /// 创建矩形
27 | DFRect(this.left, this.top, this.width, this.height) {
28 | this.right = this.left + this.width;
29 | this.bottom = this.top + this.height;
30 | }
31 |
32 | /// 创建矩形
33 | DFRect.fromCenter({ required DFPosition center, required this.width, required this.height}) {
34 | this.left = center.x - width / 2;
35 | this.top = center.y - height / 2;
36 | this.right = this.left + this.width;
37 | this.bottom = this.top + this.height;
38 | }
39 |
40 | /// 转换为Rect
41 | Rect toRect() => Rect.fromLTWH(this.left, this.top, width, height);
42 |
43 | /// 中心坐标
44 | DFPosition center() {
45 | return DFPosition(this.left + this.width / 2, this.top + this.height / 2);
46 | }
47 |
48 | /// 是否重叠
49 | @override
50 | bool overlaps(DFShape other) {
51 | if (other is DFRect) {
52 | return rectToRect(other);
53 | } else if (other is DFCircle) {
54 | return other.circleToRect(this);
55 | }
56 | return false;
57 | }
58 |
59 | /// 矩形碰撞
60 | bool rectToRect(DFRect other) {
61 | if (right <= other.left || other.right <= left) return false;
62 | if (bottom <= other.top || other.bottom <= top) return false;
63 | return true;
64 | }
65 |
66 | /// 转换为字符串
67 | @override
68 | String toString() {
69 | return "left:" +
70 | left.toString() +
71 | ",top:" +
72 | top.toString() +
73 | ",width:" +
74 | width.toString() +
75 | ",height:" +
76 | height.toString();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | //add------------------start
2 | def keystorePropertiesFile = rootProject.file("key.properties")
3 | def keystoreProperties = new Properties()
4 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
5 | //add------------------end
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterRoot = localProperties.getProperty('flutter.sdk')
16 | if (flutterRoot == null) {
17 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
18 | }
19 |
20 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
21 | if (flutterVersionCode == null) {
22 | flutterVersionCode = '1'
23 | }
24 |
25 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
26 | if (flutterVersionName == null) {
27 | flutterVersionName = '1.0'
28 | }
29 |
30 | apply plugin: 'com.android.application'
31 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
32 |
33 | android {
34 | compileSdkVersion 30
35 |
36 | defaultConfig {
37 | applicationId "dev.ym101.devilf"
38 | minSdkVersion 20
39 | targetSdkVersion 30
40 | versionCode flutterVersionCode.toInteger()
41 | versionName flutterVersionName
42 | }
43 |
44 | signingConfigs {
45 | // keystore
46 | release {
47 | keyAlias keystoreProperties['keyAlias']
48 | keyPassword keystoreProperties['keyPassword']
49 | storeFile file(keystoreProperties['storeFile'])
50 | storePassword keystoreProperties['storePassword']
51 | }
52 |
53 | }
54 |
55 | buildTypes {
56 | release {
57 | signingConfig signingConfigs.release
58 | }
59 | }
60 | }
61 |
62 | flutter {
63 | source '../..'
64 | }
65 |
--------------------------------------------------------------------------------
/lib/sprite/df_text_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 | import 'package:devilf_engine/core/df_position.dart';
3 | import 'package:devilf_engine/core/df_size.dart';
4 | import 'package:devilf_engine/sprite/df_sprite.dart';
5 | import 'package:flutter/cupertino.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | /// 文本精灵类
9 | class DFTextSprite extends DFSprite {
10 | /// 文本内容
11 | String text;
12 |
13 | /// 文字大小
14 | double fontSize;
15 |
16 | /// 文字大小
17 | Color color;
18 |
19 | /// 背景颜色
20 | Color background;
21 |
22 | /// 更新监听函数
23 | void Function(double dt)? onUpdate;
24 |
25 | /// 创建文本精灵
26 | DFTextSprite(this.text,
27 | {this.fontSize = 14,
28 | this.color = const Color(0xFFFFFFFF),
29 | this.background = const Color(0x00FFFFFF),
30 | DFSize size = const DFSize(80, 20)})
31 | : super(position: DFPosition(0, 0), size: size);
32 |
33 | /// 更新文本
34 | @override
35 | void update(double dt) {
36 | if (onUpdate != null) {
37 | onUpdate!(dt);
38 | }
39 | }
40 |
41 | /// 设置更新函数
42 | void setOnUpdate(Function(double dt) onUpdate) {
43 | this.onUpdate = onUpdate;
44 | }
45 |
46 | /// 渲染精灵
47 | @override
48 | void render(Canvas canvas) {
49 | /// 画布暂存
50 | canvas.save();
51 |
52 | /// 将子精灵转换为相对坐标
53 | if (parent == null) {
54 | canvas.translate(position.x, position.y);
55 | } else {
56 | canvas.translate(position.x - parent!.size.width / 2, position.y - parent!.size.height / 2);
57 | }
58 |
59 | /// 文本内容
60 | ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
61 | textAlign: TextAlign.center,
62 | fontStyle: FontStyle.normal,
63 | fontSize: this.fontSize,
64 | ))
65 | ..pushStyle(ui.TextStyle(color: this.color))
66 | ..addText(this.text);
67 |
68 | ui.ParagraphConstraints pc = ui.ParagraphConstraints(width: size.width);
69 | ui.Paragraph paragraph = pb.build()..layout(pc);
70 |
71 | canvas.drawParagraph(paragraph, Offset(-paragraph.width / 2, -paragraph.height / 2));
72 |
73 | /// 精灵矩形边界
74 | var paint = new Paint()..color = background;
75 | canvas.drawRect(
76 | Rect.fromLTWH(-paragraph.width / 2, -paragraph.height / 2, paragraph.width, paragraph.height), paint);
77 |
78 | ///恢复画布
79 | canvas.restore();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/tiled/df_objects.dart:
--------------------------------------------------------------------------------
1 | /// Objects节点
2 | class DFObjects {
3 | double? height;
4 | int? id;
5 | String? name;
6 | double? rotation;
7 | String? type;
8 | bool? visible;
9 | double? width;
10 | double? x;
11 | double? y;
12 | List? properties;
13 |
14 | DFObjects({
15 | this.height,
16 | this.id,
17 | this.name,
18 | this.rotation,
19 | this.type,
20 | this.visible,
21 | this.width,
22 | this.x,
23 | this.y,
24 | this.properties,
25 | });
26 |
27 | DFObjects.fromJson(Map json) {
28 | height = double.tryParse(json['height'].toString()) ?? 0.0;
29 | id = json['id'];
30 | name = json['name'];
31 | rotation = double.tryParse(json['rotation'].toString()) ?? 0.0;
32 | type = json['type'];
33 | visible = json['visible'];
34 | width = double.tryParse(json['width'].toString()) ?? 0.0;
35 | x = double.tryParse(json['x'].toString()) ?? 0.0;
36 | y = double.tryParse(json['y'].toString()) ?? 0.0;
37 | if (json['properties'] != null) {
38 | properties = [];
39 | json['properties'].forEach((v) {
40 | properties?.add(new Property.fromJson(v));
41 | });
42 | }
43 | }
44 |
45 | Map toJson() {
46 | final Map data = new Map();
47 | data['height'] = this.height;
48 | data['id'] = this.id;
49 | data['name'] = this.name;
50 | data['rotation'] = this.rotation;
51 | data['type'] = this.type;
52 | data['visible'] = this.visible;
53 | data['width'] = this.width;
54 | data['x'] = this.x;
55 | data['y'] = this.y;
56 | if (this.properties != null) {
57 | data['properties'] = this.properties?.map((v) => v.toJson()).toList();
58 | }
59 | return data;
60 | }
61 | }
62 |
63 | class Property {
64 | String? name;
65 | String? type;
66 | dynamic? value;
67 |
68 | Property({this.name, this.type, this.value});
69 |
70 | Property.fromJson(Map json) {
71 | name = json['name'];
72 | type = json['type'];
73 | value = json['value'];
74 | }
75 |
76 | Map toJson() {
77 | final Map data = new Map();
78 | data['name'] = this.name;
79 | data['type'] = this.type;
80 | data['value'] = this.value;
81 | return data;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/core/df_circle.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'package:devilf_engine/util/df_util.dart';
3 | import 'df_position.dart';
4 | import 'df_rect.dart';
5 | import 'df_shape.dart';
6 |
7 | /// 圆形
8 | class DFCircle extends DFShape {
9 | /// 中心坐标
10 | final DFPosition center;
11 |
12 | /// 半径
13 | final double radius;
14 |
15 | /// 创建圆形
16 | DFCircle(this.center, this.radius);
17 |
18 | /// 转换为Rect
19 | DFRect toRect() => DFRect(this.center.x - radius, this.center.y - radius, radius * 2, radius * 2);
20 |
21 | /// 是否重叠
22 | @override
23 | bool overlaps(DFShape other) {
24 | if (other is DFRect) {
25 | return circleToRect(other);
26 | } else if (other is DFCircle) {
27 | return circleToCircle(other);
28 | }
29 | return false;
30 | }
31 |
32 | /// 圆形和矩形重叠
33 | bool circleToRect(DFRect other) {
34 | /// 矩形不重叠,不用判断圆形了
35 | if (!toRect().overlaps(other)) {
36 | return false;
37 | }
38 |
39 | /// 排除矩形包含了圆形
40 | if (other.right >= this.center.x + this.radius &&
41 | other.left <= this.center.x - this.radius &&
42 | other.top <= this.center.y - this.radius &&
43 | other.bottom >= this.center.y + this.radius) {
44 | return true;
45 | }
46 |
47 | /// 顶点
48 | final points = [
49 | DFPosition(other.left, other.top),
50 | DFPosition(other.right, other.top),
51 | DFPosition(other.right, other.bottom),
52 | DFPosition(other.left, other.bottom),
53 | DFPosition(other.left, other.top),
54 | ];
55 |
56 | /// print(other.toString());
57 | for (var i = 0; i < points.length - 1; i++) {
58 | final distance = DFUtil.getNearestDistance(points[i], points[i + 1], this.center);
59 |
60 | /// print((distance).toString());
61 | if (DFUtil.fixDouble4(distance) <= this.radius) {
62 | return true;
63 | }
64 | }
65 | return false;
66 | }
67 |
68 | /// 圆形与圆形重叠
69 | bool circleToCircle(DFCircle other) {
70 | if (!this.toRect().rectToRect(other.toRect())) return false;
71 | final distance = this.radius + other.radius;
72 | final w = this.center.x - other.center.x;
73 | final h = this.center.y - other.center.y;
74 | return sqrt(w * w + h * h) <= distance;
75 | }
76 |
77 | /// 转换字符串
78 | @override
79 | String toString() {
80 | return "center:" + center.toString() + ",radius:" + radius.toString();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/game/df_sprite_render_box.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/gestures.dart';
4 | import 'package:flutter/rendering.dart';
5 | import 'package:flutter/scheduler.dart';
6 | import 'package:flutter/widgets.dart' hide WidgetBuilder;
7 | import 'df_game_loop.dart';
8 | import 'df_sprite_widget.dart';
9 |
10 | /// 精灵渲染盒
11 | class DFSpriteRenderBox extends RenderBox with WidgetsBindingObserver {
12 | /// 上下文
13 | BuildContext context;
14 |
15 | /// 精灵控件
16 | DFSpriteWidget spriteWidget;
17 |
18 | /// 游戏循环
19 | DFGameLoop? gameLoop;
20 |
21 | /// 创建渲染盒
22 | DFSpriteRenderBox(this.context, this.spriteWidget) {
23 | WidgetsBinding.instance!.addTimingsCallback(spriteWidget.onTimingsCallback);
24 | }
25 |
26 | /// 附加
27 | @override
28 | void attach(PipelineOwner owner) {
29 | super.attach(owner);
30 |
31 | ///启动游戏循环
32 | this.gameLoop = DFGameLoop(gameUpdate);
33 | this.gameLoop?.start();
34 |
35 | ///绑定生命周期监听
36 | bindLifecycleListener();
37 | }
38 |
39 | /// 取消附加
40 | @override
41 | void detach() {
42 | super.detach();
43 | gameLoop?.dispose();
44 | gameLoop = null;
45 | unbindLifecycleListener();
46 | }
47 |
48 | /// 游戏循环更新
49 | void gameUpdate(double dt) {
50 | if (!attached) {
51 | return;
52 | }
53 | spriteWidget.update(dt);
54 | markNeedsPaint();
55 | }
56 |
57 | /// 绘制界面
58 | @override
59 | void paint(PaintingContext context, Offset offset) {
60 | context.canvas.save();
61 | context.canvas.translate(offset.dx, offset.dy);
62 | spriteWidget.render(context.canvas);
63 | context.canvas.restore();
64 | }
65 |
66 | /// 事件分发
67 | @override
68 | bool hitTest(HitTestResult result, {required Offset position}) {
69 | if (size.contains(position)) {
70 | result.add(BoxHitTestEntry(this, position));
71 | return true;
72 | }
73 | return false;
74 | }
75 |
76 | /// 事件分发
77 | @override
78 | bool hitTestSelf(Offset position) => true;
79 |
80 | /// 重绘
81 | @override
82 | bool get isRepaintBoundary => true;
83 |
84 | /// 计算尺寸
85 | @override
86 | void performLayout() {
87 | size = constraints.biggest;
88 | }
89 |
90 | /// 计算布局
91 | @override
92 | Size computeDryLayout(BoxConstraints constraints) => constraints.biggest;
93 |
94 | /// 状态改变
95 | @override
96 | void didChangeAppLifecycleState(AppLifecycleState state) {
97 | spriteWidget.lifecycleStateChange(state);
98 | }
99 |
100 | /// 监听Widget状态
101 | void bindLifecycleListener() {
102 | WidgetsBinding.instance!.addObserver(this);
103 | }
104 |
105 | /// 不监听Widget状态
106 | void unbindLifecycleListener() {
107 | WidgetsBinding.instance!.removeObserver(this);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/sprite/df_progress_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'package:devilf_engine/core/df_position.dart';
2 | import 'package:devilf_engine/core/df_size.dart';
3 | import 'package:devilf_engine/sprite/df_sprite.dart';
4 | import 'package:flutter/cupertino.dart';
5 | import 'dart:ui' as ui;
6 | import 'package:flutter/material.dart';
7 |
8 | /// 进度精灵类
9 | class DFProgressSprite extends DFSprite {
10 | /// 精灵图片
11 | ui.Image image;
12 |
13 | /// 当前进度
14 | int progress = 100;
15 |
16 | /// 最大进度
17 | int maxProgress = 100;
18 |
19 | /// 显示进度文本
20 | bool showText;
21 |
22 | /// 文本位置
23 | DFGravity gravity;
24 |
25 | /// 文本位置偏移
26 | double textOffset;
27 |
28 | /// 创建
29 | DFProgressSprite(this.image,
30 | {this.showText = true, this.gravity = DFGravity.center, this.textOffset = 0, DFSize size = const DFSize(47, 8)})
31 | : super(position: DFPosition(0, 0), size: size);
32 |
33 | /// 精灵更新
34 | @override
35 | void update(double dt) {}
36 |
37 | /// 精灵渲染
38 | @override
39 | void render(Canvas canvas) {
40 | canvas.save();
41 |
42 | /// 将子精灵转换为相对坐标
43 | if (parent == null) {
44 | canvas.translate(position.x, position.y);
45 | } else {
46 | canvas.translate(position.x - parent!.size.width / 2, position.y - parent!.size.height / 2);
47 | }
48 |
49 | Rect srcRect = Rect.fromLTWH(0, this.size.height + 2, this.size.width, this.size.height);
50 | Rect dstRect =
51 | Rect.fromLTWH(-size.width / 2 * scale, -size.height / 2 * scale, size.width * scale, size.height * scale);
52 |
53 | Rect srcRectFill = Rect.fromLTWH(0, 0, this.size.width, this.size.height);
54 | Rect dstRectFill = Rect.fromLTWH(
55 | -size.width / 2 * scale, -size.height / 2 * scale, size.width * progress / 100 * scale, size.height * scale);
56 |
57 | /// 绘制图像
58 | Paint paint = Paint()..color = Color(0xFFFFFFFF);
59 | canvas.drawImageRect(this.image, srcRect, dstRect, paint);
60 | canvas.drawImageRect(this.image, srcRectFill, dstRectFill, paint);
61 |
62 | /// 文本内容
63 | ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
64 | textAlign: TextAlign.center,
65 | fontStyle: FontStyle.normal,
66 | fontSize: 8,
67 | ))
68 | ..pushStyle(ui.TextStyle(color: Colors.white))
69 | ..addText(progress.toString() + "/" + maxProgress.toString());
70 |
71 | ui.ParagraphConstraints pc = ui.ParagraphConstraints(width: size.width * scale * 1.5);
72 | ui.Paragraph paragraph = pb.build()..layout(pc);
73 |
74 | /// 精灵矩形边界
75 | ///var paint5 = new Paint()..color = Color(0x60000000);
76 | ///canvas.drawRect(Rect.fromLTWH(-paragraph.width/2,-paragraph.height/2, paragraph.width, paragraph.height), paint5);
77 |
78 | if (this.gravity == DFGravity.top) {
79 | canvas.drawParagraph(paragraph, Offset(-paragraph.width / 2, -paragraph.height - textOffset));
80 | } else if (this.gravity == DFGravity.center) {
81 | canvas.drawParagraph(paragraph, Offset(-paragraph.width / 2, -paragraph.height / 2));
82 | }
83 |
84 | canvas.restore();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/game/df_game_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'package:devilf_engine/core/df_circle.dart';
3 | import 'package:devilf_engine/core/df_position.dart';
4 | import 'package:devilf_engine/sprite/df_sprite.dart';
5 | import 'package:flutter/material.dart';
6 | import 'df_camera.dart';
7 | import 'df_sprite_widget.dart';
8 |
9 | /// 游戏控件
10 | class DFGameWidget extends StatelessWidget {
11 | /// 尺寸
12 | final Size size;
13 |
14 | /// 摄像机
15 | final DFCamera camera;
16 |
17 | /// 精灵控件
18 | late DFSpriteWidget spriteWidget;
19 |
20 | /// 点击抬起
21 | Function(DFSprite sprite)? onTap;
22 |
23 | /// 当前帧数
24 | double fps = 60;
25 |
26 | /// 创建游戏控件
27 | DFGameWidget({
28 | this.size = const Size(100, 100),
29 | required this.camera,
30 | this.onTap,
31 | }) {
32 | this.spriteWidget = DFSpriteWidget(
33 | camera: this.camera,
34 | onTimingsCallback: (List timings) {
35 | this.fps = 1 / (timings[0].totalSpan.inMilliseconds / 1000.0);
36 | });
37 | }
38 |
39 | /// 增加精灵 增加进来精灵才能被绘制
40 | void addChild(DFSprite? sprite) {
41 | this.spriteWidget.addChild(sprite);
42 | }
43 |
44 | /// 插入精灵 增加进来精灵才能被绘制
45 | void insertChild(int index, DFSprite? sprite) {
46 | this.spriteWidget.insertChild(index, sprite);
47 | }
48 |
49 | /// 增加精灵 增加进来精灵才能被绘制
50 | void addChildren(List sprites) {
51 | this.spriteWidget.addChildren(sprites);
52 | }
53 |
54 | /// 插入精灵 增加进来精灵才能被绘制
55 | void insertChildren(int index, List sprites) {
56 | this.spriteWidget.insertChildren(index, sprites);
57 | }
58 |
59 | /// 删除精灵
60 | void removeChild(DFSprite sprite) {
61 | this.spriteWidget.removeChild(sprite);
62 | }
63 |
64 | /// 屏幕坐标转换为世界坐标
65 | DFPosition screenToWorldPosition(Offset localPosition){
66 | if(this.camera.sprite != null){
67 | /// 屏幕上的坐标转换为实际坐标 计算出屏幕的0点的实际地图坐标
68 | double moveX = this.camera.sprite!.position.x - this.camera.rect.width / 2;
69 | double moveY = this.camera.sprite!.position.y - this.camera.rect.height / 2;
70 | return DFPosition(localPosition.dx + moveX, localPosition.dy + moveY);
71 | }else{
72 | return DFPosition(localPosition.dx, localPosition.dy);
73 | }
74 | }
75 |
76 | /// 点击监听
77 | void onTapUp(TapUpDetails details) {
78 | print("监听到点击:" + details.localPosition.toString());
79 | for (int i = this.spriteWidget.children.length - 1; i >= 0; i--) {
80 | DFSprite sprite = this.spriteWidget.children[i];
81 | /// 屏幕坐标转换为世界坐标
82 | DFPosition center = screenToWorldPosition(details.localPosition);
83 | if (sprite.getCollisionShape().overlaps(DFCircle(center, 5))) {
84 | if (this.onTap != null) {
85 | this.onTap!(sprite);
86 | }
87 | return;
88 | }
89 | }
90 | }
91 |
92 | /// 控件布局
93 | @override
94 | Widget build(BuildContext context) {
95 | return GestureDetector(
96 | child: Container(
97 | width: this.size.width,
98 | height: this.size.height,
99 | child: spriteWidget,
100 | ),
101 | onTapUp: this.onTapUp,
102 | );
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: A new Flutter project.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.12.0 <3.0.0"
22 |
23 | dependencies:
24 | flutter:
25 | sdk: flutter
26 |
27 |
28 | # The following adds the Cupertino Icons font to your application.
29 | # Use with the CupertinoIcons class for iOS style icons.
30 | cupertino_icons: ^1.0.2
31 | devilf_engine:
32 | path: ../../devilf_engine/
33 |
34 | dev_dependencies:
35 | flutter_test:
36 | sdk: flutter
37 |
38 | # For information on the generic Dart part of this file, see the
39 | # following page: https://dart.dev/tools/pub/pubspec
40 |
41 | # The following section is specific to Flutter.
42 | flutter:
43 |
44 | # The following line ensures that the Material Icons font is
45 | # included with your application, so that you can use the icons in
46 | # the material Icons class.
47 | uses-material-design: true
48 | assets:
49 | - assets/images/
50 |
51 | # To add assets to your application, add an assets section, like this:
52 | # assets:
53 | # - images/a_dot_burr.jpeg
54 | # - images/a_dot_ham.jpeg
55 |
56 | # An image asset can refer to one or more resolution-specific "variants", see
57 | # https://flutter.dev/assets-and-images/#resolution-aware.
58 |
59 | # For details regarding adding assets from package dependencies, see
60 | # https://flutter.dev/assets-and-images/#from-packages
61 |
62 | # To add custom fonts to your application, add a fonts section here,
63 | # in this "flutter" section. Each entry in this list should have a
64 | # "family" key with the font family name, and a "fonts" key with a
65 | # list giving the asset and other descriptors for the font. For
66 | # example:
67 | # fonts:
68 | # - family: Schyler
69 | # fonts:
70 | # - asset: fonts/Schyler-Regular.ttf
71 | # - asset: fonts/Schyler-Italic.ttf
72 | # style: italic
73 | # - family: Trajan Pro
74 | # fonts:
75 | # - asset: fonts/TrajanPro.ttf
76 | # - asset: fonts/TrajanPro_Bold.ttf
77 | # weight: 700
78 | #
79 | # For details regarding fonts from package dependencies,
80 | # see https://flutter.dev/custom-fonts/#from-packages
81 |
--------------------------------------------------------------------------------
/lib/game/df_animation.dart:
--------------------------------------------------------------------------------
1 | /// 动作常量
2 | class DFAction {
3 | static const String NONE = "NONE";
4 | static const String IDLE = "IDLE";
5 | static const String RUN = "RUN";
6 | static const String ATTACK = "ATTACK";
7 | static const String CASTING = "CASTING";
8 | static const String COLLECT = "COLLECT";
9 | static const String PICKUP = "PICKUP";
10 | static const String DEATH = "DEATH";
11 | static const String TRACK = "TRACK";
12 | static const String EXPLODE = "EXPLODE";
13 | }
14 |
15 | /// 方向常量
16 | class DFDirection {
17 | static const String NONE = "_NONE";
18 | static const String LEFT = "_LEFT";
19 | static const String UP_LEFT = "_UP_LEFT";
20 | static const String UP = "_UP";
21 | static const String UP_RIGHT = "_UP_RIGHT";
22 | static const String RIGHT = "_RIGHT";
23 | static const String DOWN_RIGHT = "_DOWN_RIGHT";
24 | static const String DOWN = "_DOWN";
25 | static const String DOWN_LEFT = "_DOWN_LEFT";
26 | }
27 |
28 | /// 动画常量
29 | class DFAnimation {
30 | static final List keys = [
31 | DFAction.IDLE + DFDirection.LEFT, //0
32 | DFAction.IDLE + DFDirection.UP_LEFT, //1
33 | DFAction.IDLE + DFDirection.UP, //2
34 | DFAction.IDLE + DFDirection.UP_RIGHT, //3
35 | DFAction.IDLE + DFDirection.RIGHT, //4
36 | DFAction.IDLE + DFDirection.DOWN_RIGHT, //5
37 | DFAction.IDLE + DFDirection.DOWN, //6
38 | DFAction.IDLE + DFDirection.DOWN_LEFT, //7
39 |
40 | DFAction.RUN + DFDirection.LEFT, //8
41 | DFAction.RUN + DFDirection.UP_LEFT, //9
42 | DFAction.RUN + DFDirection.UP, //10
43 | DFAction.RUN + DFDirection.UP_RIGHT, //11
44 | DFAction.RUN + DFDirection.RIGHT, //12
45 | DFAction.RUN + DFDirection.DOWN_RIGHT, //13
46 | DFAction.RUN + DFDirection.DOWN, //14
47 | DFAction.RUN + DFDirection.DOWN_LEFT, //15
48 |
49 | DFAction.ATTACK + DFDirection.LEFT, //16
50 | DFAction.ATTACK + DFDirection.UP_LEFT, //17
51 | DFAction.ATTACK + DFDirection.UP, //18
52 | DFAction.ATTACK + DFDirection.UP_RIGHT, //19
53 | DFAction.ATTACK + DFDirection.RIGHT, //20
54 | DFAction.ATTACK + DFDirection.DOWN_RIGHT, //21
55 | DFAction.ATTACK + DFDirection.DOWN, //22
56 | DFAction.ATTACK + DFDirection.DOWN_LEFT, //23
57 |
58 | DFAction.CASTING + DFDirection.LEFT, //24
59 | DFAction.CASTING + DFDirection.UP_LEFT, //25
60 | DFAction.CASTING + DFDirection.UP, //26
61 | DFAction.CASTING + DFDirection.UP_RIGHT, //27
62 | DFAction.CASTING + DFDirection.RIGHT, //28
63 | DFAction.CASTING + DFDirection.DOWN_RIGHT, //29
64 | DFAction.CASTING + DFDirection.DOWN, //30
65 | DFAction.CASTING + DFDirection.DOWN_LEFT, //31
66 |
67 | DFAction.COLLECT + DFDirection.LEFT, //32
68 | DFAction.COLLECT + DFDirection.UP_LEFT, //33
69 | DFAction.COLLECT + DFDirection.UP, //34
70 | DFAction.COLLECT + DFDirection.UP_RIGHT, //35
71 | DFAction.COLLECT + DFDirection.RIGHT, //36
72 | DFAction.COLLECT + DFDirection.DOWN_RIGHT, //37
73 | DFAction.COLLECT + DFDirection.DOWN, //38
74 | DFAction.COLLECT + DFDirection.DOWN_LEFT, //39
75 |
76 | DFAction.DEATH + DFDirection.LEFT, //40
77 | DFAction.DEATH + DFDirection.UP_LEFT, //41
78 | DFAction.DEATH + DFDirection.UP, //42
79 | DFAction.DEATH + DFDirection.UP_RIGHT, //43
80 | DFAction.DEATH + DFDirection.RIGHT, //44
81 | DFAction.DEATH + DFDirection.DOWN_RIGHT, //45
82 | DFAction.DEATH + DFDirection.DOWN, //46
83 | DFAction.DEATH + DFDirection.DOWN_LEFT, //47
84 |
85 | DFAction.TRACK + DFDirection.UP, //48
86 | DFAction.EXPLODE + DFDirection.UP, //49
87 | ];
88 | }
89 |
--------------------------------------------------------------------------------
/lib/widget/df_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 |
4 | /// 按钮
5 | class DFButton extends StatefulWidget {
6 | final String? text;
7 | final double fontSize;
8 | final Color textColor;
9 | final FontWeight fontWeight;
10 | final Size size;
11 | final String? image;
12 | final String? pressedImage;
13 | final void Function(DFButton button) onPressed;
14 |
15 | DFButton({
16 | this.text,
17 | this.fontSize = 14,
18 | this.textColor = const Color(0xFFFFFFFF),
19 | this.fontWeight = FontWeight.normal,
20 | this.size = const Size(80, 80),
21 | this.image,
22 | this.pressedImage,
23 | required this.onPressed,
24 | });
25 |
26 | @override
27 | _DFButtonState createState() => _DFButtonState();
28 | }
29 |
30 | class _DFButtonState extends State {
31 | Color? _color;
32 |
33 | @override
34 | void initState() {
35 | super.initState();
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | return SizedBox(
41 | width: widget.size.width,
42 | height: widget.size.height,
43 | child: GestureDetector(
44 | onTap: () {
45 | widget.onPressed(widget);
46 | },
47 | onTapDown: (detail) {
48 | setState(() {
49 | this._color = Colors.black54;
50 | });
51 | },
52 | onTapCancel: () {
53 | setState(() {
54 | this._color = null;
55 | });
56 | },
57 | onTapUp: (detail) {
58 | setState(() {
59 | this._color = null;
60 | });
61 | },
62 | child: Stack(fit: StackFit.expand, children: [
63 | widget.image != null
64 | ? Positioned(
65 | top: 0,
66 | left: 0,
67 | child: Container(
68 | child: Image.asset(
69 | widget.pressedImage != null ? widget.pressedImage! : widget.image!,
70 | fit: BoxFit.fill,
71 | width: widget.size.width,
72 | height: widget.size.height,
73 | color: this._color,
74 | colorBlendMode: BlendMode.dstIn,
75 | ),
76 | ),
77 | )
78 | : Container(),
79 | widget.text != null
80 | ? Positioned(
81 | top: 0,
82 | left: 0,
83 | width: widget.size.width,
84 | height: widget.size.height,
85 | child: Column(
86 | mainAxisAlignment: MainAxisAlignment.center,
87 | crossAxisAlignment: CrossAxisAlignment.center,
88 | children: [
89 | Text(
90 | widget.text!,
91 | textAlign: TextAlign.center,
92 | style: TextStyle(
93 | color: widget.textColor,
94 | fontSize: widget.fontSize,
95 | fontWeight: widget.fontWeight,
96 | ),
97 | ),
98 | ],
99 | ),
100 | )
101 | : Container(),
102 | ]),
103 | ));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/widget/df_check_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 |
4 | /// 选择按钮
5 | class DFCheckButton extends StatefulWidget {
6 | final int value;
7 | final String? text;
8 | final double fontSize;
9 | final Color textColor;
10 | final FontWeight fontWeight;
11 | final Size size;
12 | final String image;
13 | final String checkedImage;
14 | final Function(DFCheckButton button,bool checked, int value) onChanged;
15 |
16 | Function setChecked = (bool checked) {
17 | print("DFCheckButton setChecked:" + checked.toString());
18 | };
19 |
20 | DFCheckButton({
21 | required this.value,
22 | required this.onChanged,
23 | required this.image,
24 | required this.checkedImage,
25 | this.text,
26 | this.fontSize = 14,
27 | this.textColor = const Color(0xFFFFFFFF),
28 | this.fontWeight = FontWeight.normal,
29 | this.size = const Size(80, 80),
30 | });
31 |
32 | @override
33 | _DFCheckButtonState createState() => _DFCheckButtonState();
34 | }
35 |
36 | class _DFCheckButtonState extends State {
37 | Color? _color;
38 | bool _checked = false;
39 |
40 | @override
41 | void initState() {
42 | super.initState();
43 | widget.setChecked = this.setChecked;
44 | }
45 |
46 | void setChecked(bool checked) {
47 | print("setChecked:" + checked.toString());
48 | setState(() {
49 | _checked = checked;
50 | });
51 | }
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | return SizedBox(
56 | width: widget.size.width,
57 | height: widget.size.height,
58 | child: GestureDetector(
59 | onTap: () {
60 | setState(() {
61 | this._checked = !this._checked;
62 | });
63 | widget.onChanged(widget,this._checked, widget.value);
64 | },
65 | onTapDown: (detail) {
66 | setState(() {
67 | this._color = Colors.black54;
68 | });
69 | },
70 | onTapCancel: () {
71 | setState(() {
72 | this._color = null;
73 | });
74 | },
75 | onTapUp: (detail) {
76 | setState(() {
77 | this._color = null;
78 | });
79 | },
80 | child: Row(
81 | mainAxisAlignment: MainAxisAlignment.center,
82 | crossAxisAlignment: CrossAxisAlignment.center,
83 | children: [
84 | Container(
85 | child: Image.asset(
86 | _checked ? widget.checkedImage : widget.image,
87 | fit: BoxFit.fill,
88 | width: widget.size.width,
89 | height: widget.size.height,
90 | color: this._color,
91 | colorBlendMode: BlendMode.dstIn,
92 | ),
93 | ),
94 | widget.text != null?
95 | Container(
96 | padding: EdgeInsets.only(left: 5),
97 | child: Text(
98 | widget.text!,
99 | textAlign: TextAlign.center,
100 | style: TextStyle(
101 | color: widget.textColor,
102 | fontSize: widget.fontSize,
103 | fontWeight: widget.fontWeight,
104 | ),
105 | ),
106 | ):Container(),
107 | ]),
108 | ));
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/lib/sprite/df_image_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'package:devilf_engine/core/df_offset.dart';
3 | import 'package:devilf_engine/core/df_position.dart';
4 | import 'package:devilf_engine/core/df_rect.dart';
5 | import 'package:devilf_engine/core/df_size.dart';
6 | import 'package:devilf_engine/game/df_assets_loader.dart';
7 | import 'package:devilf_engine/sprite/df_sprite.dart';
8 | import 'package:flutter/cupertino.dart';
9 | import 'dart:ui' as ui;
10 |
11 | /// 图片精灵类
12 | class DFImageSprite extends DFSprite {
13 | /// 精灵图片
14 | ui.Image image;
15 |
16 | /// 截取图片区域
17 | DFRect rect;
18 |
19 | /// 坐标偏移
20 | DFOffset offset;
21 |
22 | /// 是否旋转
23 | bool rotated;
24 |
25 | /// x轴镜像
26 | bool flippedX;
27 |
28 | /// 混合模式
29 | BlendMode blendMode;
30 |
31 | /// 颜色
32 | Color color;
33 |
34 | /// 创建图片精灵
35 | DFImageSprite(
36 | this.image, {
37 | DFSize size = const DFSize(64, 64),
38 | required this.rect,
39 | this.offset = const DFOffset(0, 0),
40 | this.color = const Color(0xFFFFFFFF),
41 | this.rotated = false,
42 | this.flippedX = false,
43 | this.blendMode = BlendMode.srcOver,
44 | }) : super(position: DFPosition(0, 0), size: size);
45 |
46 | /// 加载图片资源
47 | static Future load(String src) async {
48 | ui.Image image = await DFAssetsLoader.loadImage(src);
49 | return DFImageSprite(image, rect: DFRect(0, 0, image.width.toDouble(), image.height.toDouble()));
50 | }
51 |
52 | /// 精灵渲染
53 | /// 代码没几行,坐标计算有点复杂
54 | @override
55 | void render(Canvas canvas) {
56 | /// 画布暂存
57 | canvas.save();
58 |
59 | /// 目标位置
60 | Rect dstRect;
61 |
62 | if (rotated) {
63 | /// 将子精灵转换为相对坐标
64 | if (parent == null) {
65 | canvas.translate(position.y, position.x);
66 | } else {
67 | canvas.translate(position.y - parent!.size.height / 2, position.x - parent!.size.width / 2);
68 | }
69 |
70 | /// 针对json中的图像旋转
71 | canvas.rotate(-90 * pi / 180); //弧度
72 |
73 | dstRect = Rect.fromCenter(center: Offset(0, 0), width: rect.width * scale, height: rect.height * scale);
74 | } else {
75 | /// 将子精灵转换为相对坐标
76 | if (parent == null) {
77 | canvas.translate(position.x, position.y);
78 | } else {
79 | canvas.translate(position.x - parent!.size.width / 2, position.y - parent!.size.height / 2);
80 | }
81 | canvas.rotate(this.angle * pi / 180); //弧度
82 |
83 | dstRect =
84 | Rect.fromCenter(center: Offset(0, -rect.height / 2), width: rect.width * scale, height: rect.height * scale);
85 | }
86 |
87 | /// 水平镜像
88 | if (flippedX) {
89 | if (rotated) {
90 | canvas.scale(1, -1); //左右镜像翻转
91 | } else {
92 | canvas.scale(-1, 1); //左右镜像翻转
93 | }
94 | }
95 |
96 | /// 目标绘制位置
97 | if (rotated) {
98 | dstRect = Rect.fromCenter(center: Offset(offset.dx, offset.dy), width: rect.width, height: rect.height);
99 | } else {
100 | dstRect = Rect.fromCenter(center: Offset(offset.dx, -offset.dy), width: rect.width, height: rect.height);
101 | }
102 |
103 | /// 处理缩放
104 | Rect outputRect = Rect.fromCenter(
105 | center: Offset(dstRect.center.dx * scale, dstRect.center.dy * scale),
106 | width: dstRect.width * scale,
107 | height: dstRect.height * scale);
108 |
109 | /// 绘制图像
110 | Paint paintImage = Paint()..color = color;
111 | paintImage.blendMode = this.blendMode;
112 | canvas.drawImageRect(this.image, rect.toRect(), outputRect, paintImage);
113 |
114 | /// 画布恢复
115 | canvas.restore();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/tiled/df_tile_map.dart:
--------------------------------------------------------------------------------
1 | import 'df_group_layer.dart';
2 | import 'df_image_layer.dart';
3 | import 'df_layer_type.dart';
4 | import 'df_map_layer.dart';
5 | import 'df_object_group.dart';
6 | import 'df_tile.dart';
7 | import 'df_tile_layer.dart';
8 | import 'df_tile_set.dart';
9 |
10 | /// 瓦片地图
11 | class DFTileMap {
12 | String? backgroundColor;
13 | int? compressionLevel;
14 | int? height;
15 | int? hexSideLength;
16 | bool? infinite;
17 | List? layers;
18 | int? nextLayerId;
19 | int? nextObjectId;
20 | String? orientation;
21 | String? renderOrder;
22 | String? staggerAxis;
23 | String? staggerIndex;
24 | String? tiledVersion;
25 | int? tileHeight;
26 | List? tileSets;
27 | int? tileWidth;
28 | String? type;
29 | double? version;
30 | int? width;
31 |
32 | DFTileMap({
33 | this.compressionLevel,
34 | this.height,
35 | this.infinite,
36 | this.layers,
37 | this.nextLayerId,
38 | this.nextObjectId,
39 | this.orientation,
40 | this.renderOrder,
41 | this.tiledVersion,
42 | this.tileHeight,
43 | this.tileSets,
44 | this.tileWidth,
45 | this.type,
46 | this.version,
47 | this.width,
48 | });
49 |
50 | DFTileMap.fromJson(Map json) {
51 | compressionLevel = json['compressionlevel'];
52 | height = json['height'];
53 | infinite = json['infinite'];
54 | if (json['layers'] != null) {
55 | layers = [];
56 | json['layers'].forEach((v) {
57 | if (v['type'] == DFLayerType.tileLayer) {
58 | layers?.add(DFTileLayer.fromJson(v));
59 | } else if (v['type'] == DFLayerType.objectGroup) {
60 | layers?.add(DFObjectGroup.fromJson(v));
61 | } else if (v['type'] == DFLayerType.imageLayer) {
62 | layers?.add(DFImageLayer.fromJson(v));
63 | } else if (v['type'] == DFLayerType.group) {
64 | layers?.add(DFGroupLayer.fromJson(v));
65 | } else {
66 | layers?.add(DFMapLayer.fromJson(v));
67 | }
68 | });
69 | }
70 | nextLayerId = json['nextlayerid'];
71 | nextObjectId = json['nextobjectid'];
72 | orientation = json['orientation'];
73 | renderOrder = json['renderorder'];
74 | tiledVersion = json['tiledversion'];
75 | tileHeight = json['tileheight'];
76 | if (json['tilesets'] != null) {
77 | tileSets = [];
78 | json['tilesets'].forEach((v) {
79 | tileSets?.add(DFTileSet.fromJson(v));
80 | });
81 | }
82 | tileWidth = json['tilewidth'];
83 | type = json['type'];
84 | version = double.tryParse(json['version'].toString()) ?? 0.0;
85 | width = json['width'];
86 | backgroundColor = json['backgroundcolor'];
87 | hexSideLength = json['hexsidelength'];
88 | staggerAxis = json['staggeraxis'];
89 | staggerIndex = json['staggerindex'];
90 | }
91 |
92 | Map toJson() {
93 | final Map data = new Map();
94 | data['compressionlevel'] = this.compressionLevel;
95 | data['height'] = this.height;
96 | data['infinite'] = this.infinite;
97 | if (this.layers != null) {
98 | data['layers'] = this.layers?.map((v) => v.toJson()).toList();
99 | }
100 | data['nextlayerid'] = this.nextLayerId;
101 | data['nextobjectid'] = this.nextObjectId;
102 | data['orientation'] = this.orientation;
103 | data['renderorder'] = this.renderOrder;
104 | data['tiledversion'] = this.tiledVersion;
105 | data['tileheight'] = this.tileHeight;
106 | if (this.tileSets != null) {
107 | data['tilesets'] = this.tileSets?.map((v) => v.toJson()).toList();
108 | }
109 | data['tilewidth'] = this.tileWidth;
110 | data['type'] = this.type;
111 | data['version'] = this.version;
112 | data['width'] = this.width;
113 | return data;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/game/df_sprite_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'package:devilf_engine/sprite/df_sprite.dart';
3 | import 'package:flutter/material.dart';
4 | import 'df_camera.dart';
5 | import 'df_sprite_render_box.dart';
6 |
7 | /// 精灵渲染控件(游戏主循环)
8 | /// 继承LeafRenderObjectWidget实现RenderBox可以使用canvas实现Widget
9 | class DFSpriteWidget extends LeafRenderObjectWidget {
10 | /// 摄像机
11 | final DFCamera camera;
12 |
13 | /// 游戏里的所有精灵
14 | final List children = [];
15 |
16 | /// 时间回调
17 | final Function(List timings) onTimingsCallback;
18 |
19 | /// 创建游戏控件
20 | DFSpriteWidget({required this.camera, required this.onTimingsCallback});
21 |
22 | /// 增加精灵 增加进来精灵才能被绘制
23 | void addChild(DFSprite? sprite) {
24 | if (sprite != null) {
25 | children.add(sprite);
26 | }
27 | }
28 |
29 | /// 插入精灵 增加进来精灵才能被绘制
30 | void insertChild(int index, DFSprite? sprite) {
31 | if (sprite != null) {
32 | children.insert(index, sprite);
33 | }
34 | }
35 |
36 | /// 增加精灵 增加进来精灵才能被绘制
37 | void addChildren(List sprites) {
38 | children.addAll(sprites);
39 | }
40 |
41 | /// 插入精灵 增加进来精灵才能被绘制
42 | void insertChildren(int index, List sprites) {
43 | sprites.forEach((element) {
44 | children.insert(index, element);
45 | });
46 | }
47 |
48 | /// 删除精灵
49 | void removeChild(DFSprite sprite) {
50 | /// 不能直接remove会并发修改错误
51 | sprite.visible = false;
52 | sprite.recyclable = true;
53 | }
54 |
55 | /// 创建GameRenderBox
56 | @override
57 | RenderBox createRenderObject(BuildContext context) {
58 | return DFSpriteRenderBox(context, this);
59 | }
60 |
61 | /// 设置Game到GameRenderBox
62 | @override
63 | void updateRenderObject(BuildContext context, DFSpriteRenderBox renderObject) {
64 | renderObject.spriteWidget = this;
65 | }
66 |
67 | /// 更新界面
68 | void update(double dt) {
69 | children.forEach((sprite) {
70 | if (!sprite.visible && sprite.recyclable) {
71 | /// 将要回收的精灵不更新
72 | } else {
73 | sprite.update(dt);
74 | }
75 | });
76 |
77 | /// 清除不可见的并且需要回收的的精灵
78 | children.removeWhere((sprite) => (sprite.visible == false && sprite.recyclable));
79 | }
80 |
81 | /// 绘制界面
82 | void render(Canvas canvas) {
83 | canvas.save();
84 |
85 | /// 需要移动的位置
86 | double moveX = 0;
87 | double moveY = 0;
88 |
89 | /// 跟随摄像机的精灵
90 | if (camera.sprite != null) {
91 | if (camera.limit != null) {
92 | /// 限制边界
93 | moveX = camera.rect.width / 2 - camera.sprite!.position.x;
94 | moveY = camera.rect.height / 2 - camera.sprite!.position.y;
95 |
96 | if (camera.sprite!.position.x <= camera.rect.width / 2) {
97 | moveX = 0;
98 | }
99 | if (camera.sprite!.position.y <= camera.rect.height / 2) {
100 | moveY = 0;
101 | }
102 | if (camera.sprite!.position.x >= camera.limit!.dx - camera.rect.width / 2) {
103 | moveX = camera.rect.width - camera.limit!.dx;
104 | }
105 | if (camera.sprite!.position.y >= camera.limit!.dy - camera.rect.height / 2) {
106 | moveY = camera.rect.height - camera.limit!.dy;
107 | }
108 | } else {
109 | moveX = camera.rect.width / 2 - camera.sprite!.position.x;
110 | moveY = camera.rect.height / 2 - camera.sprite!.position.y;
111 | }
112 | canvas.translate(moveX, moveY);
113 | }
114 |
115 | children.forEach((sprite) {
116 | if (sprite.visible) {
117 | if (!sprite.fixed) {
118 | sprite.render(canvas);
119 | }
120 | }
121 | });
122 |
123 | /// 固定到屏幕位置的精灵
124 | if (camera.sprite != null) {
125 | canvas.translate(-moveX, -moveY);
126 | }
127 | children.forEach((sprite) {
128 | if (sprite.visible) {
129 | if (sprite.fixed) {
130 | sprite.render(canvas);
131 | }
132 | }
133 | });
134 |
135 | canvas.restore();
136 | }
137 |
138 | /// 生命周期发生变化
139 | void lifecycleStateChange(AppLifecycleState state) {}
140 | }
141 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/dev/ym101/devilf/MainActivity.java:
--------------------------------------------------------------------------------
1 | package dev.ym101.devilf;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 |
6 | import org.json.JSONStringer;
7 |
8 | import java.util.HashMap;
9 |
10 | import io.flutter.embedding.android.FlutterActivity;
11 | import io.flutter.embedding.engine.FlutterEngine;
12 | import io.flutter.embedding.engine.dart.DartExecutor;
13 | import io.flutter.plugin.common.MethodChannel;
14 |
15 | /**
16 | * 如果从其他页面进入Flutter页面可以像下面这样写:
17 | * Intent intent = new Intent(activity, MainActivity.class);
18 | * intent.putExtra("route","/hello");
19 | * startActivity(intent);
20 | */
21 | public class MainActivity extends FlutterActivity {
22 |
23 | private static final String FLUTTER_CHANNEL = "sample.flutter.io/flutter";
24 | private static final String NATIVE_CHANNEL = "sample.flutter.io/native";
25 | private static final String NATIVE_METHOD = "callFlutter";
26 | private static final String FLUTTER_METHOD = "callNative";
27 |
28 | FlutterEngine flutterEngine;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState){
32 | super.onCreate(savedInstanceState);
33 |
34 | //从Intent里获取route 路径
35 | String route = getIntent().getStringExtra("route");
36 | if(route == null || route == ""){
37 | route = "/";
38 | }
39 |
40 | //InitialRoute
41 | flutterEngine = getFlutterEngine();
42 | flutterEngine.getNavigationChannel().setInitialRoute(route);
43 | flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
44 |
45 | //监听Flutter调用Native
46 | MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), FLUTTER_CHANNEL);
47 | methodChannel.setMethodCallHandler((call, result) -> {
48 | if (call.method.equals(FLUTTER_METHOD)) {
49 | doCallNative((HashMap)call.arguments,result);
50 | result.success("Native接到消息:" + call.arguments);
51 | } else {
52 | result.notImplemented();
53 | }
54 | });
55 |
56 | //向Flutter传递数据
57 | callFlutter();
58 | }
59 |
60 | /**
61 | * 调用Flutter
62 | */
63 | public void callFlutter(){
64 | Log.e("MainActivity","调用Flutter start");
65 |
66 | String arguments = "{\"test\":false,\"token\":\"ASDFGHJKL\",\"userId\":1}";
67 |
68 | MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), NATIVE_CHANNEL);
69 | methodChannel.invokeMethod(NATIVE_METHOD, arguments, new MethodChannel.Result() {
70 |
71 | @Override
72 | public void success(Object o) {
73 | Log.e("MainActivity","调用Flutter success");
74 | }
75 |
76 | @Override
77 | public void error(String s, String s1, Object o) {
78 | Log.e("MainActivity","调用Flutter error");
79 | }
80 |
81 | @Override
82 | public void notImplemented() {
83 |
84 | }
85 | });
86 | }
87 |
88 | /**
89 | * 你的代码写处理逻辑
90 | * @param messageMap
91 | * @param result
92 | */
93 | public void doCallNative(HashMap messageMap,MethodChannel.Result result){
94 | String message = (String)messageMap.get("message");
95 | //你的代码
96 | }
97 |
98 | @Override
99 | protected void onResume() {
100 | super.onResume();
101 | if (flutterEngine!=null){
102 | flutterEngine.getLifecycleChannel().appIsResumed();
103 | }
104 | }
105 |
106 | @Override
107 | protected void onPause() {
108 | super.onPause();
109 | if (flutterEngine!=null){
110 | flutterEngine.getLifecycleChannel().appIsInactive();
111 | }
112 | }
113 |
114 | @Override
115 | protected void onStop() {
116 | super.onStop();
117 | if (flutterEngine!=null){
118 | flutterEngine.getLifecycleChannel().appIsPaused();
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/util/df_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'package:devilf_engine/core/df_position.dart';
3 | import 'package:devilf_engine/game/df_animation.dart';
4 |
5 | /// 工具类
6 | class DFUtil {
7 | /// 获取标准8方向
8 | static String getDirection(double radians) {
9 | /// 判断8方向
10 | String direction = DFDirection.NONE;
11 |
12 | /// 换算角度
13 | double angle = 180 / pi * radians;
14 | if (angle < 0) {
15 | angle = angle + 360;
16 | }
17 |
18 | /// 顺时针 0~360
19 | /// print("angle:" + angle.toString());
20 |
21 | /// 右边
22 | if ((angle >= 0 && angle < 22.5) || (angle >= 337.5 && angle <= 360)) {
23 | direction = DFDirection.RIGHT;
24 | }
25 |
26 | /// 右下
27 | if (angle >= 22.5 && angle < 67.5) {
28 | direction = DFDirection.DOWN_RIGHT;
29 | }
30 |
31 | /// 下
32 | if (angle >= 67.5 && angle < 112.5) {
33 | direction = DFDirection.DOWN;
34 | }
35 |
36 | /// 左下
37 | if (angle >= 112.5 && angle < 157.5) {
38 | direction = DFDirection.DOWN_LEFT;
39 | }
40 |
41 | /// 左
42 | if (angle >= 157.5 && angle < 202.5) {
43 | direction = DFDirection.LEFT;
44 | }
45 |
46 | /// 左上
47 | if (angle >= 202.5 && angle < 247.5) {
48 | direction = DFDirection.UP_LEFT;
49 | }
50 |
51 | /// 上
52 | if (angle >= 247.5 && angle < 292.5) {
53 | direction = DFDirection.UP;
54 | }
55 |
56 | /// 右上
57 | if (angle >= 292.5 && angle < 337.5) {
58 | direction = DFDirection.UP_RIGHT;
59 | }
60 |
61 | /// 返回结果
62 | return direction;
63 | }
64 |
65 | /// 获取弧度
66 | static double getRadians(String direction) {
67 | double radians = 0;
68 |
69 | if (direction == DFDirection.DOWN_RIGHT) {
70 | radians = 45 * pi / 180.0;
71 | } else if (direction == DFDirection.UP_LEFT) {
72 | radians = 225 * pi / 180.0;
73 | } else if (direction == DFDirection.UP_RIGHT) {
74 | radians = 315 * pi / 180.0;
75 | } else if (direction == DFDirection.DOWN_LEFT) {
76 | radians = 135 * pi / 180.0;
77 | } else if (direction == DFDirection.RIGHT) {
78 | radians = 0;
79 | } else if (direction == DFDirection.LEFT) {
80 | radians = 180 * pi / 180.0;
81 | } else if (direction == DFDirection.DOWN) {
82 | radians = 90 * pi / 180.0;
83 | } else if (direction == DFDirection.UP) {
84 | radians = 270 * pi / 180.0;
85 | }
86 |
87 | /// 返回结果
88 | return radians;
89 | }
90 |
91 | /// Get [o] point distance [o1] and [o2] line segment distance
92 | static double getNearestDistance(DFPosition o1, DFPosition o2, DFPosition o) {
93 | if (o1 == o || o2 == o) return 0;
94 |
95 | final a = DFUtil.distanceTo(o2, o);
96 | final b = DFUtil.distanceTo(o1, o);
97 | final c = DFUtil.distanceTo(o1, o2);
98 |
99 | if (a * a >= b * b + c * c) return b;
100 | if (b * b >= a * a + c * c) return a;
101 |
102 | // 海伦公式
103 | final l = (a + b + c) / 2;
104 | final area = sqrt(l * (l - a) * (l - b) * (l - c));
105 | return 2 * area / c;
106 | }
107 |
108 | /// 获取距离
109 | static double distanceTo(DFPosition point1, DFPosition point2) => sqrt(distanceToSquared(point1, point2));
110 |
111 | /// 距离的平方
112 | static double distanceToSquared(DFPosition point1, DFPosition point2) {
113 | final dx = point1.x - point2.x;
114 | final dy = point1.y - point2.y;
115 |
116 | return dx * dx + dy * dy;
117 | }
118 |
119 | /// 将double转换为4位小数
120 | static double fixDouble4(double value) {
121 | return double.parse(value.toStringAsFixed(4));
122 | }
123 |
124 | /// 转换为二维数组
125 | static List> to2dList(List data,int columnSize,int fillValue) {
126 | int rowSize = (data.length / columnSize).ceil();
127 | List> dataNew = List.generate(rowSize,(index)=> List.generate(columnSize, (index) => 0));
128 | /// print(dataNew.toString());
129 | for (int i = 0; i < data.length; i++) {
130 | int row = (i / columnSize).floor();
131 | int column = i % columnSize;
132 | /// print(row.toString() +","+ column.toString()+ ",value:" + data[i].toString());
133 | if (data[i] != 0) {
134 | dataNew[row][column] = 1;
135 | } else {
136 | dataNew[row][column] = 0;
137 | }
138 | }
139 | return dataNew;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/lib/util/df_audio.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:audioplayers/audioplayers.dart';
4 | import 'package:flutter/cupertino.dart';
5 | import 'dart:io';
6 | import 'package:flutter/widgets.dart';
7 |
8 | /// 音效播放
9 | class DFAudio {
10 |
11 | static String prefix = "assets/audio/";
12 |
13 | /// 音频类
14 | static AudioCache audioCache = AudioCache(prefix: prefix);
15 |
16 | /// 计数
17 | int count = 0;
18 |
19 | /// 定时播放
20 | Timer? timer;
21 |
22 | /// 开始播放音效序列
23 | void startPlay(List files, {stepTime = 400, loop = false}) {
24 | if(files.length == 0){
25 | return;
26 | }
27 | count = 0;
28 | timer = Timer.periodic(Duration(milliseconds: stepTime), (t) {
29 | if (count == files.length) {
30 | if (loop) {
31 | count = 0;
32 | String file = files.elementAt(count);
33 | play(file);
34 | } else {
35 | timer!.cancel();
36 | timer = null;
37 | }
38 | } else {
39 | String file = files.elementAt(count);
40 | play(file);
41 | }
42 | count++;
43 | });
44 | }
45 |
46 | /// 取消播放音效序列
47 | void stopPlay() {
48 | if (timer != null) {
49 | timer!.cancel();
50 | timer = null;
51 | count = 0;
52 | }
53 | }
54 |
55 | /// 背景音乐
56 | static BackgroundMusic backgroundMusic = BackgroundMusic(audioCache: audioCache);
57 |
58 | /// 播放短音频
59 | static Future play(String file, {double volume = 1.0}) {
60 | return audioCache.play(file, volume: volume, mode: PlayerMode.LOW_LATENCY);
61 | }
62 |
63 | /// 循环播放
64 | static Future loop(String file, {double volume = 1.0}) {
65 | return audioCache.loop(file, volume: volume, mode: PlayerMode.LOW_LATENCY);
66 | }
67 |
68 | /// 播放长音频
69 | static Future playLongAudio(String file, {double volume = 1.0}) {
70 | return audioCache.play(file, volume: volume);
71 | }
72 |
73 | /// 循环播放长音频
74 | static Future loopLongAudio(String file, {double volume = 1.0}) {
75 | return audioCache.loop(file, volume: volume);
76 | }
77 | }
78 |
79 | /// 背景音乐
80 | class BackgroundMusic extends WidgetsBindingObserver {
81 | bool _isRegistered = false;
82 | late AudioCache audioCache;
83 | AudioPlayer? audioPlayer;
84 | bool isPlaying = false;
85 |
86 | BackgroundMusic({AudioCache? audioCache}) : audioCache = audioCache ?? AudioCache();
87 |
88 | void initialize() {
89 | if (_isRegistered) {
90 | return;
91 | }
92 | _isRegistered = true;
93 | WidgetsBinding.instance?.addObserver(this);
94 | }
95 |
96 | Future play(String filename, {double volume = 1}) async {
97 | final currentPlayer = audioPlayer;
98 | if (currentPlayer != null && currentPlayer.state != PlayerState.STOPPED) {
99 | currentPlayer.stop();
100 | }
101 |
102 | isPlaying = true;
103 | audioPlayer = await audioCache.loop(filename, volume: volume);
104 | }
105 |
106 | Future load(String file) => audioCache.load(file);
107 |
108 | Future loadAsFile(String file) => audioCache.loadAsFile(file);
109 |
110 | Future> loadAll(List files) => audioCache.loadAll(files);
111 |
112 | void clear(Uri file) => audioCache.clear(file);
113 |
114 | void clearAll() => audioCache.clearAll();
115 |
116 | Future stop() async {
117 | isPlaying = false;
118 | if (audioPlayer != null) {
119 | await audioPlayer!.stop();
120 | }
121 | }
122 |
123 | Future resume() async {
124 | if (audioPlayer != null) {
125 | isPlaying = true;
126 | await audioPlayer!.resume();
127 | }
128 | }
129 |
130 | Future pause() async {
131 | if (audioPlayer != null) {
132 | isPlaying = false;
133 | await audioPlayer!.pause();
134 | }
135 | }
136 |
137 | void dispose() {
138 | if (!_isRegistered) {
139 | return;
140 | }
141 | WidgetsBinding.instance?.removeObserver(this);
142 | _isRegistered = false;
143 | }
144 |
145 | @override
146 | void didChangeAppLifecycleState(AppLifecycleState state) {
147 | if (state == AppLifecycleState.resumed) {
148 | if (isPlaying && audioPlayer?.state == PlayerState.PAUSED) {
149 | audioPlayer?.resume();
150 | }
151 | } else {
152 | audioPlayer?.pause();
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/example/lib/scene/game_scene.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:devilf_engine/core/df_position.dart';
3 | import 'package:devilf_engine/core/df_rect.dart';
4 | import 'package:devilf_engine/core/df_size.dart';
5 | import 'package:devilf_engine/game/df_camera.dart';
6 | import 'package:devilf_engine/game/df_game_widget.dart';
7 | import 'package:devilf_engine/sprite/df_image_sprite.dart';
8 | import 'package:devilf_engine/sprite/df_text_sprite.dart';
9 | import 'package:example/building/stone_sprite.dart';
10 | import 'package:flutter/cupertino.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:flutter/services.dart';
13 | import '../game_manager.dart';
14 |
15 | class GameScene extends StatefulWidget {
16 | final int map;
17 |
18 | GameScene({this.map = 1});
19 |
20 | @override
21 | _GameSceneState createState() => _GameSceneState();
22 | }
23 |
24 | class _GameSceneState extends State with TickerProviderStateMixin {
25 | /// 主界面
26 | DFGameWidget? _gameWidget;
27 |
28 | /// 加载状态
29 | bool _loading = true;
30 |
31 | /// 创建主场景
32 | _GameSceneState();
33 |
34 | /// 初始化状态
35 | @override
36 | void initState() {
37 | super.initState();
38 |
39 | /// 强制横屏
40 | SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
41 |
42 | /// 加载游戏
43 | _loadGame();
44 | }
45 |
46 | /// 开始进入游戏
47 | void _loadGame() async {
48 | try {
49 | await Future.delayed(Duration(seconds: 1), () async {
50 | /// 摄像机
51 | DFCamera camera = DFCamera(rect: DFRect(0, 0, GameManager.visibleWidth, GameManager.visibleHeight));
52 |
53 | /// 定义主界面
54 | this._gameWidget =
55 | DFGameWidget(size: Size(GameManager.visibleWidth, GameManager.visibleHeight), camera: camera);
56 |
57 | /// Logo精灵
58 | DFImageSprite logoSprite = await DFImageSprite.load("assets/images/sprite.png");
59 | logoSprite.scale = 0.4;
60 | logoSprite.size = DFSize(40, 40);
61 | logoSprite.position =
62 | DFPosition(MediaQuery.of(context).size.width - 110, MediaQuery.of(context).padding.top + 30);
63 | logoSprite.fixed = true;
64 |
65 | /// 帧数精灵
66 | DFTextSprite fpsSprite = DFTextSprite("60 fps");
67 | fpsSprite.position =
68 | DFPosition(MediaQuery.of(context).size.width - 70, MediaQuery.of(context).padding.top + 30);
69 | fpsSprite.fixed = true;
70 | fpsSprite.setOnUpdate((dt) {
71 | fpsSprite.text = this._gameWidget!.fps.toStringAsFixed(0) + " fps";
72 | });
73 |
74 | /// 演示精灵
75 | StoneSprite stoneSprite = StoneSprite();
76 | stoneSprite.scale = 4;
77 | stoneSprite.position =
78 | DFPosition(MediaQuery.of(context).size.width / 2, MediaQuery.of(context).size.height / 2);
79 |
80 | /// 将Logo精灵添加到主界面
81 | this._gameWidget!.addChild(logoSprite);
82 |
83 | /// 将帧数精灵添加到主界面
84 | this._gameWidget!.addChild(fpsSprite);
85 |
86 | /// 将演示精灵精灵添加到主界面
87 | this._gameWidget!.addChild(stoneSprite);
88 |
89 | /// 设置摄像机跟随
90 | camera.lookAt(stoneSprite);
91 |
92 | /// 保存到管理器里
93 | GameManager.gameWidget = this._gameWidget!;
94 |
95 | /// Loading完成
96 | setState(() {
97 | _loading = false;
98 | });
99 |
100 | print("游戏加载完成...");
101 | });
102 | } catch (e) {
103 | print('(GameScene _loadGame) Error: $e');
104 | }
105 | }
106 |
107 | /// Loading显示
108 | Widget _loadingWidget() {
109 | return Center(
110 | child:
111 | Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
112 | CircularProgressIndicator(),
113 | Padding(
114 | padding: EdgeInsets.only(top: 16),
115 | child: Text(
116 | "Loading...",
117 | style: TextStyle(
118 | color: Colors.white,
119 | fontSize: 14,
120 | ),
121 | ),
122 | ),
123 | ]),
124 | );
125 | }
126 |
127 | @override
128 | Widget build(BuildContext context) {
129 | /// 获取屏幕尺寸
130 | GameManager.visibleWidth = MediaQuery.of(context).size.width;
131 | GameManager.visibleHeight = MediaQuery.of(context).size.height;
132 | print("获取屏幕尺寸:" + GameManager.visibleWidth.toString() + "," + GameManager.visibleHeight.toString());
133 |
134 | return Container(
135 | color: Colors.black87,
136 | child: _loading
137 | ? _loadingWidget()
138 | : Stack(fit: StackFit.expand, children: [
139 | /// 游戏控件
140 | Positioned(
141 | top: 0,
142 | left: 0,
143 | child: Container(
144 | width: MediaQuery.of(context).size.width,
145 | height: MediaQuery.of(context).size.height,
146 | color: Colors.black87,
147 | child: _gameWidget,
148 | ),
149 | ),
150 |
151 | /// Logo
152 | Positioned(
153 | left: 20,
154 | top: MediaQuery.of(context).padding.top + 20,
155 | child: Text(
156 | "DevilF",
157 | style: TextStyle(
158 | color: Colors.white,
159 | fontSize: 14,
160 | ),
161 | ),
162 | ),
163 | ]),
164 | );
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/lib/widget/df_joystick.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:devilf_engine/game/df_animation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | /// 摇杆控制
7 | // ignore: must_be_immutable
8 | class DFJoyStick extends StatefulWidget {
9 | /// 背景图
10 | final String? backgroundImage;
11 |
12 | /// 手柄图
13 | final String? handleImage;
14 |
15 | /// 背景颜色
16 | final Color backgroundColor;
17 |
18 | /// 手柄颜色
19 | final Color handleColor;
20 |
21 | /// 遥杆尺寸
22 | final Size size;
23 |
24 | /// 监听区域
25 | final Size areaSize;
26 |
27 | /// 当前方向
28 | String direction;
29 |
30 | /// 方向更新
31 | final void Function(String, double) onChange;
32 |
33 | /// 操作取消
34 | final void Function(String) onCancel;
35 |
36 | /// 创建遥杆
37 | DFJoyStick({
38 | this.handleImage,
39 | this.backgroundImage,
40 | this.handleColor = const Color(0x60FFFFFF),
41 | this.backgroundColor = const Color(0x40FFFFFF),
42 | this.direction = DFDirection.NONE,
43 | required this.onChange,
44 | required this.onCancel,
45 | this.size = const Size(100, 100),
46 | this.areaSize = const Size(240, 240),
47 | });
48 |
49 | @override
50 | State createState() => DFJoyStickState();
51 | }
52 |
53 | class DFJoyStickState extends State {
54 | /// 偏移量
55 | Offset backgroundOffset = Offset.zero;
56 | Offset handleOffset = Offset.zero;
57 |
58 | /// 更新位置
59 | void updateDirection(double radians) {
60 | /// 判断8方向
61 | String direction = DFDirection.NONE;
62 |
63 | /// 换算角度
64 | double angle = 180 / pi * radians;
65 | if (angle < 0) {
66 | angle = angle + 360;
67 | }
68 |
69 | /// 顺时针 0~360
70 | /// print("angle:" + angle.toString());
71 |
72 | /// 右边
73 | if ((angle >= 0 && angle < 22.5) || (angle >= 337.5 && angle <= 360)) {
74 | direction = DFDirection.RIGHT;
75 | }
76 |
77 | /// 右下
78 | if (angle >= 22.5 && angle < 67.5) {
79 | direction = DFDirection.DOWN_RIGHT;
80 | }
81 |
82 | /// 下
83 | if (angle >= 67.5 && angle < 112.5) {
84 | direction = DFDirection.DOWN;
85 | }
86 |
87 | /// 左下
88 | if (angle >= 112.5 && angle < 157.5) {
89 | direction = DFDirection.DOWN_LEFT;
90 | }
91 |
92 | /// 左
93 | if (angle >= 157.5 && angle < 202.5) {
94 | direction = DFDirection.LEFT;
95 | }
96 |
97 | /// 左上
98 | if (angle >= 202.5 && angle < 247.5) {
99 | direction = DFDirection.UP_LEFT;
100 | }
101 |
102 | /// 上
103 | if (angle >= 247.5 && angle < 292.5) {
104 | direction = DFDirection.UP;
105 | }
106 |
107 | /// 右上
108 | if (angle >= 292.5 && angle < 337.5) {
109 | direction = DFDirection.UP_RIGHT;
110 | }
111 |
112 | /// 返回结果
113 | if (direction != widget.direction) {
114 | widget.direction = direction;
115 | widget.onChange(widget.direction, radians);
116 | }
117 | }
118 |
119 | /// 计算移动距离
120 | void calculateMove(Offset localPosition) {
121 | /// 相对位置
122 | Offset offset = localPosition - Offset(widget.areaSize.width / 2, widget.areaSize.height / 2) - backgroundOffset;
123 |
124 | /// 更新摇杆位置 活动范围控制在Size之内
125 | Offset handle = Offset.fromDirection(offset.direction, min(widget.size.width / 2, offset.distance));
126 | setState(() {
127 | handleOffset = handle;
128 | });
129 |
130 | /// [-pi,pi]
131 | updateDirection(offset.direction);
132 | }
133 |
134 | @override
135 | Widget build(BuildContext context) {
136 | return Container(
137 | child: GestureDetector(
138 | child: Container(
139 | width: widget.areaSize.width,
140 | height: widget.areaSize.height,
141 | color: Color(0x00000000),
142 | child: Center(
143 | child: Transform.translate(
144 | offset: backgroundOffset,
145 | child: Container(
146 | width: widget.size.width,
147 | height: widget.size.height,
148 | decoration: BoxDecoration(
149 | color: widget.backgroundColor,
150 | image: widget.backgroundImage != null
151 | ? DecorationImage(image: AssetImage(widget.backgroundImage!))
152 | : null,
153 | borderRadius: BorderRadius.circular(widget.size.width / 2),
154 | ),
155 | child: Center(
156 | child: Transform.translate(
157 | offset: handleOffset,
158 | child: Container(
159 | width: widget.size.width / 2,
160 | height: widget.size.height / 2,
161 | decoration: BoxDecoration(
162 | color: widget.handleColor,
163 | image:
164 | widget.handleImage != null ? DecorationImage(image: AssetImage(widget.handleImage!)) : null,
165 | borderRadius: BorderRadius.circular(widget.size.width / 4),
166 | ),
167 | ),
168 | ),
169 | ),
170 | ),
171 | ),
172 | ),
173 | ),
174 | onPanDown: onDragDown,
175 | onPanUpdate: onDragUpdate,
176 | onPanEnd: onDragEnd,
177 | ),
178 | );
179 | }
180 |
181 | void onDragDown(DragDownDetails details) {
182 | /// 按下
183 | setState(() {
184 | backgroundOffset = details.localPosition - Offset(widget.areaSize.width / 2, widget.areaSize.height / 2);
185 | handleOffset = Offset.zero;
186 | });
187 | }
188 |
189 | void onDragUpdate(DragUpdateDetails details) {
190 | /// 移动
191 | calculateMove(details.localPosition);
192 | }
193 |
194 | void onDragEnd(DragEndDetails details) {
195 | /// 抬起
196 | setState(() {
197 | backgroundOffset = Offset.zero;
198 | handleOffset = Offset.zero;
199 | });
200 |
201 | /// 返回结果
202 | widget.onCancel(widget.direction);
203 |
204 | /// 归位
205 | widget.direction = DFDirection.NONE;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/lib/util/df_astar.dart:
--------------------------------------------------------------------------------
1 | import 'dart:core';
2 |
3 | import 'package:devilf_engine/core/df_position.dart';
4 |
5 | /// A*算法类
6 | class DFAStar {
7 | static const int BAR = 1; // 障碍
8 | static const int PATH = 2; // 路径
9 | static const int DIRECT_VALUE = 10; // 横竖移动代价
10 | static const int OBLIQUE_VALUE = 14; // 斜移动代价
11 |
12 | List _openList = [];
13 | List _closeList = [];
14 | List _pathList = [];
15 |
16 | /// 开始算法
17 | Future> start(List> blockMap, DFAStarNode startNode, DFAStarNode endNode) async {
18 | /// Map数据
19 | DFAStarMap map = DFAStarMap(blockMap, blockMap[0].length, blockMap.length, startNode, endNode);
20 |
21 | /// clean
22 | _openList.clear();
23 | _closeList.clear();
24 | _pathList.clear();
25 |
26 | /// 开始搜索
27 | _openList.add(map.start);
28 |
29 | /// 优先队列(升序)
30 | _openList.sort((a, b) => a.compareTo(b));
31 | moveNodes(map);
32 |
33 | /// 删掉起点
34 | if (this._pathList.length > 0) {
35 | this._pathList.removeLast();
36 | }
37 |
38 | /// 返回路径
39 | return this._pathList;
40 | }
41 |
42 | /// 移动当前结点
43 | void moveNodes(DFAStarMap map) {
44 | while (_openList.length > 0) {
45 | /// 第一个元素
46 | DFAStarNode current = _openList.removeAt(0);
47 | _closeList.add(current);
48 | addNeighborNodeInOpen(map, current);
49 | if (isPositionInClose(map.end.position)) {
50 | drawPath(map.maps, map.end);
51 | break;
52 | }
53 | }
54 | }
55 |
56 | /// 在二维数组中绘制路径
57 | void drawPath(List>? maps, DFAStarNode? end) {
58 | if (end == null || maps == null) return;
59 | print("总代价:" + end.G.toString());
60 | while (end != null) {
61 | DFTilePosition c = end.position!;
62 | maps[c.y][c.x] = PATH;
63 | end = end.parent;
64 | _pathList.add(DFTilePosition(c.x, c.y));
65 | }
66 | }
67 |
68 | /// 添加所有邻结点到open表
69 | void addNeighborNodeInOpen(DFAStarMap mapInfo, DFAStarNode current) {
70 | int x = current.position!.x;
71 | int y = current.position!.y;
72 | // 左
73 | addNeighborNodeInOpenXy(mapInfo, current, x - 1, y, DIRECT_VALUE);
74 | // 上
75 | addNeighborNodeInOpenXy(mapInfo, current, x, y - 1, DIRECT_VALUE);
76 | // 右
77 | addNeighborNodeInOpenXy(mapInfo, current, x + 1, y, DIRECT_VALUE);
78 | // 下
79 | addNeighborNodeInOpenXy(mapInfo, current, x, y + 1, DIRECT_VALUE);
80 | // 左上
81 | addNeighborNodeInOpenXy(mapInfo, current, x - 1, y - 1, OBLIQUE_VALUE);
82 | // 右上
83 | addNeighborNodeInOpenXy(mapInfo, current, x + 1, y - 1, OBLIQUE_VALUE);
84 | // 右下
85 | addNeighborNodeInOpenXy(mapInfo, current, x + 1, y + 1, OBLIQUE_VALUE);
86 | // 左下
87 | addNeighborNodeInOpenXy(mapInfo, current, x - 1, y + 1, OBLIQUE_VALUE);
88 | }
89 |
90 | /// 添加一个邻结点到open表
91 | void addNeighborNodeInOpenXy(DFAStarMap mapInfo, DFAStarNode current, int x, int y, int value) {
92 | if (canAddNodeToOpen(mapInfo, x, y)) {
93 | DFAStarNode end = mapInfo.end;
94 | DFTilePosition position = DFTilePosition(x, y);
95 | int G = current.G + value; // 计算邻结点的G值
96 | DFAStarNode? child = findNodeInOpen(position);
97 | if (child == null) {
98 | int H = calcH(end.position!, position); // 计算H值
99 | if (isEndNode(end.position!, position)) {
100 | child = end;
101 | child.parent = current;
102 | child.G = G;
103 | child.H = H;
104 | } else {
105 | child = DFAStarNode.newNode(position, current, G, H);
106 | }
107 | _openList.add(child);
108 | _openList.sort((a, b) => a.compareTo(b));
109 | } else if (child.G > G) {
110 | child.G = G;
111 | child.parent = current;
112 | _openList.add(child);
113 | _openList.sort((a, b) => a.compareTo(b));
114 | }
115 | }
116 | }
117 |
118 | /// 从Open列表中查找结点
119 | DFAStarNode? findNodeInOpen(DFTilePosition? position) {
120 | if (position == null || _openList.length == 0) return null;
121 | for (DFAStarNode node in _openList) {
122 | if (node.position! == position) {
123 | return node;
124 | }
125 | }
126 | return null;
127 | }
128 |
129 | /// 计算H的估值:“曼哈顿”法,坐标分别取差值相加
130 | int calcH(DFTilePosition end, DFTilePosition coord) {
131 | return ((end.x - coord.x).abs() + (end.y - coord.y).abs()) * DIRECT_VALUE;
132 | }
133 |
134 | /// 判断结点是否是最终结点
135 | bool isEndNode(DFTilePosition end, DFTilePosition? position) {
136 | return position != null && end == position;
137 | }
138 |
139 | /// 判断结点能否放入Open列表
140 | bool canAddNodeToOpen(DFAStarMap mapInfo, int x, int y) {
141 | /// 是否在地图中
142 | if (x < 0 || x >= mapInfo.width || y < 0 || y >= mapInfo.height) return false;
143 |
144 | /// 判断是否是不可通过的结点
145 | if (mapInfo.maps![y][x] == BAR) return false;
146 |
147 | /// 判断结点是否存在close表
148 | if (isCoordInCloseXy(x, y)) return false;
149 |
150 | return true;
151 | }
152 |
153 | /// 判断坐标是否在close表中
154 | bool isPositionInClose(DFTilePosition? position) {
155 | if (position == null) {
156 | return false;
157 | }
158 | return isCoordInCloseXy(position.x, position.y);
159 | }
160 |
161 | /// 判断坐标是否在close表中
162 | bool isCoordInCloseXy(int x, int y) {
163 | if (_closeList.length == 0) {
164 | return false;
165 | }
166 | for (DFAStarNode node in _closeList) {
167 | if (node.position!.x == x && node.position!.y == y) {
168 | return true;
169 | }
170 | }
171 | return false;
172 | }
173 | }
174 |
175 | /// 包含地图所需的所有输入数据
176 | class DFAStarMap {
177 | /// 二维数组的地图
178 | List>? maps;
179 |
180 | /// 地图的宽
181 | int width;
182 |
183 | /// 地图的高
184 | int height;
185 |
186 | /// 起始结点
187 | DFAStarNode start;
188 |
189 | /// 最终结点
190 | DFAStarNode end;
191 |
192 | DFAStarMap(this.maps, this.width, this.height, this.start, this.end);
193 | }
194 |
195 | /// 路径节点
196 | class DFAStarNode {
197 | DFTilePosition? position; // 坐标
198 | DFAStarNode? parent; // 父结点
199 | int G = 0; // G:是个准确的值,是起点到当前结点的代价
200 | int H = 0; // H:是个估值,当前结点到目的结点的估计代价
201 |
202 | DFAStarNode(int x, int y) {
203 | this.position = new DFTilePosition(x, y);
204 | }
205 |
206 | DFAStarNode.newNode(DFTilePosition position, DFAStarNode parent, int g, int h) {
207 | this.position = position;
208 | this.parent = parent;
209 | this.G = g;
210 | this.H = h;
211 | }
212 |
213 | @override
214 | String toString() {
215 | return position.toString();
216 | }
217 |
218 | /// 排序比较
219 | int compareTo(DFAStarNode other) {
220 | /// 大于
221 | if (G + H > other.G + other.H) {
222 | return 1;
223 | }
224 |
225 | /// 小于
226 | else if (G + H < other.G + other.H) {
227 | return -1;
228 | }
229 |
230 | /// 等于
231 | return 0;
232 | }
233 | }
234 |
235 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.8.1"
11 | audioplayers:
12 | dependency: "direct main"
13 | description:
14 | name: audioplayers
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "0.20.1"
18 | boolean_selector:
19 | dependency: transitive
20 | description:
21 | name: boolean_selector
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.1.0"
25 | characters:
26 | dependency: transitive
27 | description:
28 | name: characters
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.0"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.3.1"
39 | clock:
40 | dependency: transitive
41 | description:
42 | name: clock
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.1.0"
46 | collection:
47 | dependency: transitive
48 | description:
49 | name: collection
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.15.0"
53 | crypto:
54 | dependency: transitive
55 | description:
56 | name: crypto
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "3.0.1"
60 | fake_async:
61 | dependency: transitive
62 | description:
63 | name: fake_async
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "1.2.0"
67 | ffi:
68 | dependency: transitive
69 | description:
70 | name: ffi
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "1.1.2"
74 | file:
75 | dependency: transitive
76 | description:
77 | name: file
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "6.1.2"
81 | flutter:
82 | dependency: "direct main"
83 | description: flutter
84 | source: sdk
85 | version: "0.0.0"
86 | flutter_test:
87 | dependency: "direct dev"
88 | description: flutter
89 | source: sdk
90 | version: "0.0.0"
91 | flutter_web_plugins:
92 | dependency: transitive
93 | description: flutter
94 | source: sdk
95 | version: "0.0.0"
96 | http:
97 | dependency: transitive
98 | description:
99 | name: http
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "0.13.3"
103 | http_parser:
104 | dependency: transitive
105 | description:
106 | name: http_parser
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "4.0.0"
110 | js:
111 | dependency: transitive
112 | description:
113 | name: js
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "0.6.3"
117 | matcher:
118 | dependency: transitive
119 | description:
120 | name: matcher
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "0.12.10"
124 | meta:
125 | dependency: transitive
126 | description:
127 | name: meta
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "1.7.0"
131 | path:
132 | dependency: transitive
133 | description:
134 | name: path
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "1.8.0"
138 | path_provider:
139 | dependency: transitive
140 | description:
141 | name: path_provider
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "2.0.2"
145 | path_provider_linux:
146 | dependency: transitive
147 | description:
148 | name: path_provider_linux
149 | url: "https://pub.dartlang.org"
150 | source: hosted
151 | version: "2.0.2"
152 | path_provider_macos:
153 | dependency: transitive
154 | description:
155 | name: path_provider_macos
156 | url: "https://pub.dartlang.org"
157 | source: hosted
158 | version: "2.0.2"
159 | path_provider_platform_interface:
160 | dependency: transitive
161 | description:
162 | name: path_provider_platform_interface
163 | url: "https://pub.dartlang.org"
164 | source: hosted
165 | version: "2.0.1"
166 | path_provider_windows:
167 | dependency: transitive
168 | description:
169 | name: path_provider_windows
170 | url: "https://pub.dartlang.org"
171 | source: hosted
172 | version: "2.0.3"
173 | pedantic:
174 | dependency: transitive
175 | description:
176 | name: pedantic
177 | url: "https://pub.dartlang.org"
178 | source: hosted
179 | version: "1.11.1"
180 | platform:
181 | dependency: transitive
182 | description:
183 | name: platform
184 | url: "https://pub.dartlang.org"
185 | source: hosted
186 | version: "3.0.0"
187 | plugin_platform_interface:
188 | dependency: transitive
189 | description:
190 | name: plugin_platform_interface
191 | url: "https://pub.dartlang.org"
192 | source: hosted
193 | version: "2.0.1"
194 | process:
195 | dependency: transitive
196 | description:
197 | name: process
198 | url: "https://pub.dartlang.org"
199 | source: hosted
200 | version: "4.2.3"
201 | sky_engine:
202 | dependency: transitive
203 | description: flutter
204 | source: sdk
205 | version: "0.0.99"
206 | source_span:
207 | dependency: transitive
208 | description:
209 | name: source_span
210 | url: "https://pub.dartlang.org"
211 | source: hosted
212 | version: "1.8.1"
213 | stack_trace:
214 | dependency: transitive
215 | description:
216 | name: stack_trace
217 | url: "https://pub.dartlang.org"
218 | source: hosted
219 | version: "1.10.0"
220 | stream_channel:
221 | dependency: transitive
222 | description:
223 | name: stream_channel
224 | url: "https://pub.dartlang.org"
225 | source: hosted
226 | version: "2.1.0"
227 | string_scanner:
228 | dependency: transitive
229 | description:
230 | name: string_scanner
231 | url: "https://pub.dartlang.org"
232 | source: hosted
233 | version: "1.1.0"
234 | term_glyph:
235 | dependency: transitive
236 | description:
237 | name: term_glyph
238 | url: "https://pub.dartlang.org"
239 | source: hosted
240 | version: "1.2.0"
241 | test_api:
242 | dependency: transitive
243 | description:
244 | name: test_api
245 | url: "https://pub.dartlang.org"
246 | source: hosted
247 | version: "0.4.2"
248 | typed_data:
249 | dependency: transitive
250 | description:
251 | name: typed_data
252 | url: "https://pub.dartlang.org"
253 | source: hosted
254 | version: "1.3.0"
255 | uuid:
256 | dependency: transitive
257 | description:
258 | name: uuid
259 | url: "https://pub.dartlang.org"
260 | source: hosted
261 | version: "3.0.4"
262 | vector_math:
263 | dependency: transitive
264 | description:
265 | name: vector_math
266 | url: "https://pub.dartlang.org"
267 | source: hosted
268 | version: "2.1.0"
269 | win32:
270 | dependency: transitive
271 | description:
272 | name: win32
273 | url: "https://pub.dartlang.org"
274 | source: hosted
275 | version: "2.2.5"
276 | xdg_directories:
277 | dependency: transitive
278 | description:
279 | name: xdg_directories
280 | url: "https://pub.dartlang.org"
281 | source: hosted
282 | version: "0.2.0"
283 | sdks:
284 | dart: ">=2.13.0 <3.0.0"
285 | flutter: ">=2.0.0"
286 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.8.1"
11 | audioplayers:
12 | dependency: transitive
13 | description:
14 | name: audioplayers
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "0.20.1"
18 | boolean_selector:
19 | dependency: transitive
20 | description:
21 | name: boolean_selector
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.1.0"
25 | characters:
26 | dependency: transitive
27 | description:
28 | name: characters
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.0"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.3.1"
39 | clock:
40 | dependency: transitive
41 | description:
42 | name: clock
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.1.0"
46 | collection:
47 | dependency: transitive
48 | description:
49 | name: collection
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.15.0"
53 | crypto:
54 | dependency: transitive
55 | description:
56 | name: crypto
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "3.0.1"
60 | cupertino_icons:
61 | dependency: "direct main"
62 | description:
63 | name: cupertino_icons
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "1.0.3"
67 | devilf_engine:
68 | dependency: "direct main"
69 | description:
70 | path: ".."
71 | relative: true
72 | source: path
73 | version: "0.1.0"
74 | fake_async:
75 | dependency: transitive
76 | description:
77 | name: fake_async
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "1.2.0"
81 | ffi:
82 | dependency: transitive
83 | description:
84 | name: ffi
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "1.1.2"
88 | file:
89 | dependency: transitive
90 | description:
91 | name: file
92 | url: "https://pub.dartlang.org"
93 | source: hosted
94 | version: "6.1.2"
95 | flutter:
96 | dependency: "direct main"
97 | description: flutter
98 | source: sdk
99 | version: "0.0.0"
100 | flutter_test:
101 | dependency: "direct dev"
102 | description: flutter
103 | source: sdk
104 | version: "0.0.0"
105 | flutter_web_plugins:
106 | dependency: transitive
107 | description: flutter
108 | source: sdk
109 | version: "0.0.0"
110 | http:
111 | dependency: transitive
112 | description:
113 | name: http
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "0.13.3"
117 | http_parser:
118 | dependency: transitive
119 | description:
120 | name: http_parser
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "4.0.0"
124 | js:
125 | dependency: transitive
126 | description:
127 | name: js
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "0.6.3"
131 | matcher:
132 | dependency: transitive
133 | description:
134 | name: matcher
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "0.12.10"
138 | meta:
139 | dependency: transitive
140 | description:
141 | name: meta
142 | url: "https://pub.dartlang.org"
143 | source: hosted
144 | version: "1.7.0"
145 | path:
146 | dependency: transitive
147 | description:
148 | name: path
149 | url: "https://pub.dartlang.org"
150 | source: hosted
151 | version: "1.8.0"
152 | path_provider:
153 | dependency: transitive
154 | description:
155 | name: path_provider
156 | url: "https://pub.dartlang.org"
157 | source: hosted
158 | version: "2.0.2"
159 | path_provider_linux:
160 | dependency: transitive
161 | description:
162 | name: path_provider_linux
163 | url: "https://pub.dartlang.org"
164 | source: hosted
165 | version: "2.0.2"
166 | path_provider_macos:
167 | dependency: transitive
168 | description:
169 | name: path_provider_macos
170 | url: "https://pub.dartlang.org"
171 | source: hosted
172 | version: "2.0.2"
173 | path_provider_platform_interface:
174 | dependency: transitive
175 | description:
176 | name: path_provider_platform_interface
177 | url: "https://pub.dartlang.org"
178 | source: hosted
179 | version: "2.0.1"
180 | path_provider_windows:
181 | dependency: transitive
182 | description:
183 | name: path_provider_windows
184 | url: "https://pub.dartlang.org"
185 | source: hosted
186 | version: "2.0.3"
187 | pedantic:
188 | dependency: transitive
189 | description:
190 | name: pedantic
191 | url: "https://pub.dartlang.org"
192 | source: hosted
193 | version: "1.11.1"
194 | platform:
195 | dependency: transitive
196 | description:
197 | name: platform
198 | url: "https://pub.dartlang.org"
199 | source: hosted
200 | version: "3.0.0"
201 | plugin_platform_interface:
202 | dependency: transitive
203 | description:
204 | name: plugin_platform_interface
205 | url: "https://pub.dartlang.org"
206 | source: hosted
207 | version: "2.0.1"
208 | process:
209 | dependency: transitive
210 | description:
211 | name: process
212 | url: "https://pub.dartlang.org"
213 | source: hosted
214 | version: "4.2.3"
215 | sky_engine:
216 | dependency: transitive
217 | description: flutter
218 | source: sdk
219 | version: "0.0.99"
220 | source_span:
221 | dependency: transitive
222 | description:
223 | name: source_span
224 | url: "https://pub.dartlang.org"
225 | source: hosted
226 | version: "1.8.1"
227 | stack_trace:
228 | dependency: transitive
229 | description:
230 | name: stack_trace
231 | url: "https://pub.dartlang.org"
232 | source: hosted
233 | version: "1.10.0"
234 | stream_channel:
235 | dependency: transitive
236 | description:
237 | name: stream_channel
238 | url: "https://pub.dartlang.org"
239 | source: hosted
240 | version: "2.1.0"
241 | string_scanner:
242 | dependency: transitive
243 | description:
244 | name: string_scanner
245 | url: "https://pub.dartlang.org"
246 | source: hosted
247 | version: "1.1.0"
248 | term_glyph:
249 | dependency: transitive
250 | description:
251 | name: term_glyph
252 | url: "https://pub.dartlang.org"
253 | source: hosted
254 | version: "1.2.0"
255 | test_api:
256 | dependency: transitive
257 | description:
258 | name: test_api
259 | url: "https://pub.dartlang.org"
260 | source: hosted
261 | version: "0.4.2"
262 | typed_data:
263 | dependency: transitive
264 | description:
265 | name: typed_data
266 | url: "https://pub.dartlang.org"
267 | source: hosted
268 | version: "1.3.0"
269 | uuid:
270 | dependency: transitive
271 | description:
272 | name: uuid
273 | url: "https://pub.dartlang.org"
274 | source: hosted
275 | version: "3.0.4"
276 | vector_math:
277 | dependency: transitive
278 | description:
279 | name: vector_math
280 | url: "https://pub.dartlang.org"
281 | source: hosted
282 | version: "2.1.0"
283 | win32:
284 | dependency: transitive
285 | description:
286 | name: win32
287 | url: "https://pub.dartlang.org"
288 | source: hosted
289 | version: "2.2.5"
290 | xdg_directories:
291 | dependency: transitive
292 | description:
293 | name: xdg_directories
294 | url: "https://pub.dartlang.org"
295 | source: hosted
296 | version: "0.2.0"
297 | sdks:
298 | dart: ">=2.13.0 <3.0.0"
299 | flutter: ">=2.0.0"
300 |
--------------------------------------------------------------------------------
/lib/sprite/df_animation_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:collection';
2 | import 'dart:ui';
3 | import 'package:devilf_engine/game/df_animation.dart';
4 | import 'package:devilf_engine/game/df_assets_loader.dart';
5 | import 'package:devilf_engine/core/df_offset.dart';
6 | import 'package:devilf_engine/core/df_rect.dart';
7 | import 'package:devilf_engine/sprite/df_sprite.dart';
8 | import 'package:devilf_engine/core/df_position.dart';
9 | import 'package:devilf_engine/core/df_size.dart';
10 | import 'dart:ui' as ui;
11 | import 'df_image_sprite.dart';
12 |
13 | /// 动画精灵
14 | class DFAnimationSprite extends DFSprite {
15 | /// 这个动画的全部帧
16 | Map> frames = HashMap>();
17 |
18 | /// 绑定动画
19 | List bindSprites = [];
20 |
21 | /// 当前是的Index
22 | int currentIndex = 0;
23 |
24 | /// 是否循环动画
25 | bool loop = true;
26 |
27 | /// 帧速率
28 | int stepTime;
29 |
30 | /// 当前动画类型
31 | String currentAnimation = DFAction.NONE + DFDirection.NONE;
32 |
33 | /// x轴镜像
34 | bool currentAnimationFlippedX = false;
35 |
36 | /// 颜色
37 | Color color;
38 |
39 | /// 被绑定状态
40 | bool isBind = false;
41 |
42 | /// 帧绘制时钟
43 | int frameClock = 0;
44 |
45 | /// 完成事件
46 | Function(DFAnimationSprite)? onComplete;
47 |
48 | /// 创建动画精灵
49 | DFAnimationSprite({
50 | this.stepTime = 200,
51 | this.loop = true,
52 | this.color = const Color(0xFFFFFFFF),
53 | DFSize size = const DFSize(128, 128),
54 | }) : super(position: DFPosition(0, 0), size: size);
55 |
56 | /// 将plist转换Json后读取精灵
57 | /// {
58 | /// "frames": {
59 | /// "idle_00000.png":
60 | /// {
61 | /// "frame": "{{929,291},{76,110}}",
62 | /// "offset": "{-19,24}",
63 | /// "rotated": true
64 | /// }
65 | /// }
66 | /// }
67 | static Future load(String json, {scale = 0.5, blendMode = BlendMode.srcOver}) async {
68 | print("DFAnimationSprite Load:" + json);
69 | DFAnimationSprite animationSprite = DFAnimationSprite(stepTime: 100, loop: true);
70 | DFAnimation.keys.forEach((element) {
71 | animationSprite.frames[element] = [];
72 | });
73 | Map jsonMap = await DFAssetsLoader.loadJson(json);
74 | ui.Image image = await DFAssetsLoader.loadImage(json.replaceAll(".json", ".png"));
75 | final jsonFrames = jsonMap['frames'] as Map;
76 |
77 | /// final jsonMetadata = jsonMap['metadata'] as Map;
78 | jsonFrames.forEach((key, value) {
79 | final map = value as Map;
80 |
81 | bool rotated = false;
82 | if (map['rotated'] != null) {
83 | rotated = map['rotated'] as bool;
84 | }
85 |
86 | final frame = map['frame'] as String;
87 | final offset = map['offset'] as String;
88 | List frameText = frame.replaceAll("{{", "").replaceAll("},{", ",").replaceAll("}}", "").split(",");
89 | List offsetText = offset.replaceAll("{", "").replaceAll("}", "").split(",");
90 |
91 | DFRect frameRect = DFRect(double.parse(frameText[0]), double.parse(frameText[1]), double.parse(frameText[2]),
92 | double.parse(frameText[3]));
93 | DFOffset frameOffset = DFOffset(double.parse(offsetText[0]), double.parse(offsetText[1]));
94 |
95 | /// 如果是旋转的参数也修改一下
96 | if (rotated) {
97 | frameRect = DFRect(double.parse(frameText[0]), double.parse(frameText[1]), double.parse(frameText[3]),
98 | double.parse(frameText[2]));
99 | frameOffset = DFOffset(double.parse(offsetText[1]), double.parse(offsetText[0]));
100 | }
101 |
102 | /// print("frameSize:" + frameRect.toString());
103 | /// print("frameOffset:" + frameOffset.toString());
104 |
105 | DFImageSprite sprite = DFImageSprite(
106 | image,
107 | offset: frameOffset,
108 | rect: frameRect,
109 | rotated: rotated,
110 | blendMode: blendMode,
111 | );
112 | sprite.scale = scale;
113 |
114 | /// idle_00000.png
115 | String actionText = "idle_";
116 | String action = DFAction.IDLE;
117 | if (key.contains("idle_")) {
118 | actionText = "idle_";
119 | action = DFAction.IDLE;
120 | } else if (key.contains("run_")) {
121 | actionText = "run_";
122 | action = DFAction.RUN;
123 | } else if (key.contains("attack_")) {
124 | actionText = "attack_";
125 | action = DFAction.ATTACK;
126 | } else if (key.contains("casting_")) {
127 | actionText = "casting_";
128 | action = DFAction.CASTING;
129 | } else if (key.contains("collect_")) {
130 | actionText = "collect_";
131 | action = DFAction.COLLECT;
132 | } else if (key.contains("death_")) {
133 | actionText = "death_";
134 | action = DFAction.DEATH;
135 | } else if (key.contains("track_")) {
136 | actionText = "track_";
137 | action = DFAction.TRACK;
138 | } else if (key.contains("explode_")) {
139 | actionText = "explode_";
140 | action = DFAction.EXPLODE;
141 | }
142 |
143 | String keyNumber = key.replaceAll(actionText, "").replaceAll(".png", "");
144 | if (keyNumber.startsWith("000")) {
145 | animationSprite.frames[action + DFDirection.UP]?.add(sprite);
146 | }
147 | if (keyNumber.startsWith("100")) {
148 | animationSprite.frames[action + DFDirection.UP_RIGHT]?.add(sprite);
149 | animationSprite.frames[action + DFDirection.UP_LEFT]?.add(sprite);
150 | }
151 | if (keyNumber.startsWith("200")) {
152 | animationSprite.frames[action + DFDirection.RIGHT]?.add(sprite);
153 | animationSprite.frames[action + DFDirection.LEFT]?.add(sprite);
154 | }
155 | if (keyNumber.startsWith("300")) {
156 | animationSprite.frames[action + DFDirection.DOWN_RIGHT]?.add(sprite);
157 | animationSprite.frames[action + DFDirection.DOWN_LEFT]?.add(sprite);
158 | }
159 | if (keyNumber.startsWith("400")) {
160 | animationSprite.frames[action + DFDirection.DOWN]?.add(sprite);
161 | }
162 | });
163 |
164 | return animationSprite;
165 | }
166 |
167 | /// 绑定动画同步子精灵
168 | void bindChild(DFAnimationSprite sprite) {
169 | sprite.isBind = true;
170 | sprite.position = DFPosition(sprite.position.x - size.width / 2, sprite.position.y - size.height / 2);
171 | this.bindSprites.add(sprite);
172 | }
173 |
174 | /// 移除绑定精灵
175 | void removeBindChild(DFAnimationSprite sprite) {
176 | sprite.visible = false;
177 | sprite.recyclable = true;
178 | }
179 |
180 | /// 播放动画
181 | void play(String animation, {int stepTime = 200, bool loop = true, onComplete}) {
182 | if (this.currentAnimation != animation) {
183 | this.currentIndex = 0;
184 | this.currentAnimation = animation;
185 | this.stepTime = stepTime;
186 | this.loop = loop;
187 | this.onComplete = onComplete;
188 | /*print("Play:" +
189 | animation.toString() +
190 | ",frames:" +
191 | this.frames[this.currentAnimation]!.length.toString() +
192 | ",stepTime:" +
193 | stepTime.toString());*/
194 | }
195 | }
196 |
197 | /// 精灵更新
198 | @override
199 | void update(double dt) {
200 | /// 控制动画帧切换
201 | if (this.frames[this.currentAnimation] != null && this.frames[this.currentAnimation]!.length > 0) {
202 | List sprites = this.frames[this.currentAnimation]!;
203 |
204 | /// 控制动画帧按照stepTime进行更新
205 | if (DateTime.now().millisecondsSinceEpoch - this.frameClock > this.stepTime) {
206 | this.frameClock = DateTime.now().millisecondsSinceEpoch;
207 | if (sprites.length > this.currentIndex + 1) {
208 | this.currentIndex = this.currentIndex + 1;
209 | } else {
210 | /// 如果循环就从0再次开始
211 | if (this.loop) {
212 | this.currentIndex = 0;
213 | } else {
214 | /// 动画播放到完成
215 | if (onComplete != null) {
216 | onComplete!(this);
217 | }
218 | }
219 | }
220 | }
221 | }
222 | super.update(dt);
223 | }
224 |
225 | /// 精灵渲染
226 | @override
227 | void render(Canvas canvas) {
228 | /// 画布暂存
229 | canvas.save();
230 |
231 | /// 将子精灵转换为相对坐标
232 | if (parent == null) {
233 | canvas.translate(position.x, position.y);
234 | } else {
235 | canvas.translate(position.x - parent!.size.width / 2, position.y - parent!.size.height / 2);
236 | }
237 |
238 | /// 精灵矩形边界
239 | /// var paint = new Paint()..color = Color(0x6000FF00);
240 | /// canvas.drawRect(Rect.fromLTWH(- size.width/2,- size.height/2, size.width, size.height), paint);
241 |
242 | /// 渲染动画帧
243 | if (this.frames[this.currentAnimation] != null && this.frames[this.currentAnimation]!.length > 0) {
244 | List sprites = this.frames[this.currentAnimation]!;
245 | sprites[this.currentIndex].flippedX = currentAnimationFlippedX;
246 | sprites[this.currentIndex].color = color;
247 | sprites[this.currentIndex].render(canvas);
248 | }
249 |
250 | /// 渲染子精灵
251 | if (children.length > 0) {
252 | children.forEach((sprite) {
253 | sprite.render(canvas);
254 | });
255 | }
256 |
257 | /// 渲染绑定精灵
258 | bindSprites.forEach((sprite) {
259 | if (sprite.visible &&
260 | sprite.frames[this.currentAnimation] != null &&
261 | this.frames[this.currentAnimation] != null) {
262 | /// 绑定精灵帧数不一定完全对应 所以这里计算一下接近值,保证速度和最后动画结束时间一致
263 | double offset = sprite.frames[this.currentAnimation]!.length / this.frames[this.currentAnimation]!.length;
264 | sprite.currentIndex = (this.currentIndex * offset).round();
265 | sprite.currentAnimation = this.currentAnimation;
266 | sprite.currentAnimationFlippedX = currentAnimationFlippedX;
267 | sprite.render(canvas);
268 | }
269 | });
270 |
271 | /// 画布恢复
272 | canvas.restore();
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/lib/sprite/df_tile_map_sprite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 | import 'package:devilf_engine/core/df_circle.dart';
3 | import 'package:devilf_engine/core/df_position.dart';
4 | import 'package:devilf_engine/core/df_shape.dart';
5 | import 'package:devilf_engine/core/df_size.dart';
6 | import 'package:devilf_engine/game/df_assets_loader.dart';
7 | import 'package:devilf_engine/game/df_camera.dart';
8 | import 'package:devilf_engine/core/df_rect.dart';
9 | import 'package:devilf_engine/sprite/df_sprite.dart';
10 | import 'package:devilf_engine/tiled/df_tile.dart';
11 | import 'package:devilf_engine/tiled/df_tile_layer.dart';
12 | import 'package:devilf_engine/tiled/df_tile_set.dart';
13 | import 'package:devilf_engine/tiled/df_tile_map.dart';
14 | import 'dart:ui' as ui;
15 | import '../devilf_engine.dart';
16 | import 'df_image_sprite.dart';
17 |
18 | /// 瓦片地图精灵
19 | class DFTileMapSprite extends DFSprite {
20 |
21 | /// 瓦片地图
22 | DFTileMap? tileMap;
23 |
24 | /// 需要绘制的瓦片
25 | List sprites = [];
26 |
27 | /// 图片资源路径
28 | String imagePath = "";
29 |
30 | /// 摄像机位置
31 | DFPosition? cameraPosition;
32 |
33 | /// 地图层
34 | DFTileLayer? mapLayer;
35 |
36 | /// 遮挡层
37 | DFTileLayer? alphaLayer;
38 |
39 | /// 碰撞层
40 | DFTileLayer? blockLayer;
41 |
42 | /// 创建地图精灵
43 | DFTileMapSprite({
44 | DFSize size = const DFSize(100, 100),
45 | }) : super(position: DFPosition(0, 0), size: size);
46 |
47 | /// 读取tiled导出的json文件
48 | static Future load(String json, double scale) async {
49 | DFTileMapSprite tileMapSprite = DFTileMapSprite();
50 | Map jsonMap = await DFAssetsLoader.loadJson(json);
51 | tileMapSprite.tileMap = DFTileMap.fromJson(jsonMap);
52 | tileMapSprite.imagePath = json.substring(0, json.lastIndexOf("/"));
53 | tileMapSprite.scale = scale;
54 | tileMapSprite.tileMap!.layers!.forEach((layer) {
55 | if (layer is DFTileLayer && layer.name == "map" && layer.visible == true) {
56 | tileMapSprite.mapLayer = layer;
57 | } else if (layer is DFTileLayer && layer.name == "block") {
58 | tileMapSprite.blockLayer = layer;
59 | } else if (layer is DFTileLayer && layer.name == "alpha") {
60 | tileMapSprite.alphaLayer = layer;
61 | }
62 | });
63 | return tileMapSprite;
64 | }
65 |
66 | /// 更细地图瓦片
67 | Future updateLayer(DFCamera camera) async {
68 | if (this.tileMap == null) return;
69 |
70 | /// 上一下刷新时摄像机的位置
71 | if (this.cameraPosition != null) {
72 | if ((this.cameraPosition!.x - camera.sprite!.position.x).abs() < this.tileMap!.tileWidth! * 5 * scale &&
73 | (this.cameraPosition!.y - camera.sprite!.position.y).abs() < this.tileMap!.tileHeight! * 5 * scale) {
74 | return;
75 | }
76 | }
77 |
78 | /// 保存上一下刷新时摄像机的位置
79 | this.cameraPosition = DFPosition(camera.sprite!.position.x, camera.sprite!.position.y);
80 | double drawX = camera.sprite!.position.x - camera.rect.width;
81 | double drawY = camera.sprite!.position.y - camera.rect.height;
82 |
83 | /// 可视区域
84 | DFRect visibleRect = DFRect(drawX, drawY, camera.rect.width * 2,
85 | camera.rect.height * 2);
86 |
87 | /// print("visibleRect:" + visibleRect.toString());
88 |
89 | /// 将全部的瓦片设置为不可见
90 | sprites.forEach((element) {
91 | element.visible = false;
92 | });
93 |
94 | /// 遍历图层瓦片
95 | if (mapLayer != null) {
96 | DFTileSet? tileSet;
97 | double tileWidth = 0;
98 | double tileHeight = 0;
99 | int columnCount = 0;
100 | await Future.forEach(mapLayer!.data ?? [], (tile) async {
101 | if (tile != 0) {
102 | if (tileSet == null) {
103 | tileSet = tileMap!.tileSets!.lastWhere((element) {
104 | return element.firsTgId != null && tile >= element.firsTgId!;
105 | });
106 | columnCount = (tileMap!.width! * tileMap!.tileWidth!) ~/ tileSet!.tileWidth!.toDouble();
107 | tileWidth = tileSet!.tileWidth!.toDouble() * this.scale;
108 | tileHeight = tileSet!.tileHeight!.toDouble() * this.scale;
109 | }
110 | if (tileSet != null) {
111 | int tileIndex = tile - tileSet!.firsTgId!;
112 | int row = _getY(tileIndex, columnCount).toInt();
113 | int column = _getX(tileIndex, columnCount).toInt();
114 |
115 | /// print("row:" + row.toString() + ",column:" + column.toString() + ",scale:" + this.scale.toString());
116 | Rect tileRect = Rect.fromLTWH(column * tileWidth, row * tileHeight, tileWidth, tileHeight);
117 |
118 | /// print("tileRect:" + tileRect.toString());
119 | /// 在可视区域的瓦片设置为显示
120 | if (visibleRect.toRect().overlaps(tileRect)) {
121 | DFImageSprite? imageSprite = existImageSprite(row, column);
122 | if (imageSprite == null) {
123 | imageSprite = await getTileImageSprite(tileSet!, tileIndex, row, column, this.scale);
124 | sprites.add(imageSprite);
125 | } else {
126 | imageSprite.visible = true;
127 | }
128 | }
129 | }
130 | }
131 | });
132 | }
133 |
134 | /// 删除不可见的精灵
135 | sprites.removeWhere((element) => !element.visible);
136 | }
137 |
138 | /// 获取存在的精灵
139 | DFImageSprite? existImageSprite(int row, int column) {
140 | for (DFImageSprite sprite in sprites) {
141 | if (row.toString() + "," + column.toString() == sprite.key) {
142 | return sprite;
143 | }
144 | }
145 | return null;
146 | }
147 |
148 | /// 获取瓦片精灵的某个瓦片
149 | Future getTileImageSprite(DFTileSet tileSet, int tileIndex, int row, int column, double scale) async {
150 | ///print("index:" + index.toString());
151 |
152 | List? tiles = tileSet.tiles;
153 | String imagePath = this.imagePath + "/";
154 | double tileWidth = tileSet.tileWidth!.toDouble();
155 | double tileHeight = tileSet.tileHeight!.toDouble();
156 | double imageWidth = 0;
157 | double imageHeight = 0;
158 |
159 | if (tiles != null) {
160 | DFTile tile = tiles[tileIndex];
161 | imageWidth = tile.imageWidth!.toDouble();
162 | imageHeight = tile.imageHeight!.toDouble();
163 | imagePath = this.imagePath + "/" + tile.image!;
164 |
165 | /// print(imagePath);
166 | } else {
167 | imageWidth = tileSet.imageWidth!.toDouble();
168 | imageHeight = tileSet.imageHeight!.toDouble();
169 | imagePath = this.imagePath + "/" + tileSet.image!;
170 | }
171 |
172 | /// Image创建
173 | ui.Image image = await DFAssetsLoader.loadImage(imagePath);
174 | DFImageSprite sprite = DFImageSprite(
175 | image,
176 | rect: DFRect(0, 0, imageWidth, imageHeight),
177 | rotated: false,
178 | );
179 | sprite.scale = scale;
180 | sprite.position = DFPosition(
181 | column * tileWidth * scale + tileWidth / 2 * scale, row * tileHeight * scale + tileHeight / 2 * scale);
182 | sprite.key = row.toString() + "," + column.toString();
183 | return sprite;
184 | }
185 |
186 | /// 获取列
187 | double _getX(int index, int width) {
188 | return (index % width).toDouble();
189 | }
190 |
191 | /// 获取行
192 | double _getY(int index, int width) {
193 | return (index / width).floor().toDouble();
194 | }
195 |
196 | /// 检查碰撞和遮挡
197 | /// 遮挡1
198 | /// 碰撞2
199 | /// 没有0
200 | int isCollided(DFShape shape) {
201 | bool isBlock = false;
202 | bool isAlpha = false;
203 |
204 | if (blockLayer != null && alphaLayer != null) {
205 | List points = [];
206 | if (shape is DFRect) {
207 | points.add(DFPosition(shape.left, shape.top));
208 | points.add(DFPosition(shape.right, shape.top));
209 | points.add(DFPosition(shape.right, shape.bottom));
210 | points.add(DFPosition(shape.left, shape.bottom));
211 | } else if (shape is DFCircle) {
212 | points.add(DFPosition(shape.center.x - shape.radius, shape.center.y - shape.radius));
213 | points.add(DFPosition(shape.center.x + shape.radius, shape.center.y - shape.radius));
214 | points.add(DFPosition(shape.center.x - shape.radius, shape.center.y + shape.radius));
215 | points.add(DFPosition(shape.center.x + shape.radius, shape.center.y + shape.radius));
216 | }
217 |
218 | int columnCount = tileMap!.width!;
219 | double scaledTiledWidth = this.tileMap!.tileWidth! * this.scale;
220 | double scaledTiledHeight = this.tileMap!.tileHeight! * this.scale;
221 |
222 | /// 获取形状的4个点进行判断碰撞,比遍历性能会高很多
223 | for (int i = 0; i < points.length; i++) {
224 | int row = (points[i].y / scaledTiledHeight).ceil() - 1;
225 | int column = (points[i].x / scaledTiledWidth).ceil() - 1;
226 |
227 | /// print("row:" + row.toString() + ",column:" + column.toString());
228 | int index = row * columnCount + column;
229 |
230 | /// print("index:" + index.toString());
231 | if (blockLayer!.data![index] != 0) {
232 | isBlock = true;
233 | break;
234 | } else if (alphaLayer!.data![index] != 0) {
235 | isAlpha = true;
236 | }
237 | }
238 | }
239 | if (isBlock) {
240 | return 2;
241 | } else if (isAlpha) {
242 | return 1;
243 | }
244 | return 0;
245 | }
246 |
247 | /// 绘制碰撞层和遮挡层
248 | void drawBlockAndAlphaLayer(Canvas canvas) {
249 | if (blockLayer != null && this.cameraPosition != null) {
250 | /// 可视区域
251 | DFCircle visibleShape = DFCircle(this.cameraPosition!, 300);
252 |
253 | /// print("visibleRect:" + visibleRect.toString());
254 | int columnCount = tileMap!.width!;
255 | double tileWidth = tileMap!.tileWidth!.toDouble() * this.scale;
256 | double tileHeight = tileMap!.tileHeight!.toDouble() * this.scale;
257 |
258 | for (int i = 0; i < blockLayer!.data!.length; i++) {
259 | if (blockLayer!.data![i] != 0) {
260 | var paint = new Paint()..color = Color(0x60f05b72);
261 | int row = _getY(i, columnCount).toInt();
262 | int column = _getX(i, columnCount).toInt();
263 | DFRect tileRect = DFRect(column * tileWidth, row * tileHeight, tileWidth, tileHeight);
264 | if (visibleShape.overlaps(tileRect)) {
265 | /// 在可视区域的瓦片设置为显示
266 | canvas.drawRect(tileRect.toRect(), paint);
267 | }
268 | } else if (alphaLayer!.data![i] != 0) {
269 | var paint = new Paint()..color = Color(0x60426ab3);
270 | int row = _getY(i, columnCount).toInt();
271 | int column = _getX(i, columnCount).toInt();
272 | DFRect tileRect = DFRect(column * tileWidth, row * tileHeight, tileWidth, tileHeight);
273 | if (visibleShape.overlaps(tileRect)) {
274 | /// 在可视区域的瓦片设置为显示
275 | canvas.drawRect(tileRect.toRect(), paint);
276 | }
277 | }
278 | }
279 | }
280 | }
281 |
282 | /// 精灵更新
283 | @override
284 | void update(double dt) {}
285 |
286 | /// 精灵渲染
287 | @override
288 | void render(Canvas canvas) {
289 | /// 画布暂存
290 | canvas.save();
291 |
292 | /// 将子精灵转换为相对坐标
293 | canvas.translate(position.x, position.y);
294 |
295 | if (this.sprites.length > 0) {
296 | this.sprites.forEach((sprite) {
297 | sprite.render(canvas);
298 | });
299 | }
300 |
301 | /// 精灵矩形边界
302 | /*if(this.cameraPosition!=null){
303 | var paint = new Paint()..color = Color(0x6000FF00);
304 | DFRect visibleRect = DFRect(this.cameraPosition!.x - 50,this.cameraPosition!.y -50, 100, 100);
305 | canvas.drawRect(visibleRect.toRect(), paint);
306 | }*/
307 |
308 | /// 绘制碰撞层和遮挡层
309 | if (DFConfig.debug) {
310 | drawBlockAndAlphaLayer(canvas);
311 | }
312 |
313 | /// 画布恢复
314 | canvas.restore();
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1020;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | buildActionMask = 2147483647;
175 | files = (
176 | );
177 | inputPaths = (
178 | );
179 | name = "Thin Binary";
180 | outputPaths = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
185 | };
186 | 9740EEB61CF901F6004384FC /* Run Script */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | );
193 | name = "Run Script";
194 | outputPaths = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | shellPath = /bin/sh;
198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
199 | };
200 | /* End PBXShellScriptBuildPhase section */
201 |
202 | /* Begin PBXSourcesBuildPhase section */
203 | 97C146EA1CF9000F007C117D /* Sources */ = {
204 | isa = PBXSourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
209 | );
210 | runOnlyForDeploymentPostprocessing = 0;
211 | };
212 | /* End PBXSourcesBuildPhase section */
213 |
214 | /* Begin PBXVariantGroup section */
215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
216 | isa = PBXVariantGroup;
217 | children = (
218 | 97C146FB1CF9000F007C117D /* Base */,
219 | );
220 | name = Main.storyboard;
221 | sourceTree = "";
222 | };
223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C147001CF9000F007C117D /* Base */,
227 | );
228 | name = LaunchScreen.storyboard;
229 | sourceTree = "";
230 | };
231 | /* End PBXVariantGroup section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
240 | CLANG_CXX_LIBRARY = "libc++";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_EMPTY_BODY = YES;
250 | CLANG_WARN_ENUM_CONVERSION = YES;
251 | CLANG_WARN_INFINITE_RECURSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu99;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | SDKROOT = iphoneos;
278 | SUPPORTED_PLATFORMS = iphoneos;
279 | TARGETED_DEVICE_FAMILY = "1,2";
280 | VALIDATE_PRODUCT = YES;
281 | };
282 | name = Profile;
283 | };
284 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
285 | isa = XCBuildConfiguration;
286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CLANG_ENABLE_MODULES = YES;
290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
291 | ENABLE_BITCODE = NO;
292 | INFOPLIST_FILE = Runner/Info.plist;
293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
294 | PRODUCT_BUNDLE_IDENTIFIER = com.ymbok.example;
295 | PRODUCT_NAME = "$(TARGET_NAME)";
296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
297 | SWIFT_VERSION = 5.0;
298 | VERSIONING_SYSTEM = "apple-generic";
299 | };
300 | name = Profile;
301 | };
302 | 97C147031CF9000F007C117D /* Debug */ = {
303 | isa = XCBuildConfiguration;
304 | buildSettings = {
305 | ALWAYS_SEARCH_USER_PATHS = NO;
306 | CLANG_ANALYZER_NONNULL = YES;
307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
308 | CLANG_CXX_LIBRARY = "libc++";
309 | CLANG_ENABLE_MODULES = YES;
310 | CLANG_ENABLE_OBJC_ARC = YES;
311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
312 | CLANG_WARN_BOOL_CONVERSION = YES;
313 | CLANG_WARN_COMMA = YES;
314 | CLANG_WARN_CONSTANT_CONVERSION = YES;
315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
317 | CLANG_WARN_EMPTY_BODY = YES;
318 | CLANG_WARN_ENUM_CONVERSION = YES;
319 | CLANG_WARN_INFINITE_RECURSION = YES;
320 | CLANG_WARN_INT_CONVERSION = YES;
321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
326 | CLANG_WARN_STRICT_PROTOTYPES = YES;
327 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
328 | CLANG_WARN_UNREACHABLE_CODE = YES;
329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
331 | COPY_PHASE_STRIP = NO;
332 | DEBUG_INFORMATION_FORMAT = dwarf;
333 | ENABLE_STRICT_OBJC_MSGSEND = YES;
334 | ENABLE_TESTABILITY = YES;
335 | GCC_C_LANGUAGE_STANDARD = gnu99;
336 | GCC_DYNAMIC_NO_PIC = NO;
337 | GCC_NO_COMMON_BLOCKS = YES;
338 | GCC_OPTIMIZATION_LEVEL = 0;
339 | GCC_PREPROCESSOR_DEFINITIONS = (
340 | "DEBUG=1",
341 | "$(inherited)",
342 | );
343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
345 | GCC_WARN_UNDECLARED_SELECTOR = YES;
346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
347 | GCC_WARN_UNUSED_FUNCTION = YES;
348 | GCC_WARN_UNUSED_VARIABLE = YES;
349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
350 | MTL_ENABLE_DEBUG_INFO = YES;
351 | ONLY_ACTIVE_ARCH = YES;
352 | SDKROOT = iphoneos;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Debug;
356 | };
357 | 97C147041CF9000F007C117D /* Release */ = {
358 | isa = XCBuildConfiguration;
359 | buildSettings = {
360 | ALWAYS_SEARCH_USER_PATHS = NO;
361 | CLANG_ANALYZER_NONNULL = YES;
362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
363 | CLANG_CXX_LIBRARY = "libc++";
364 | CLANG_ENABLE_MODULES = YES;
365 | CLANG_ENABLE_OBJC_ARC = YES;
366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
367 | CLANG_WARN_BOOL_CONVERSION = YES;
368 | CLANG_WARN_COMMA = YES;
369 | CLANG_WARN_CONSTANT_CONVERSION = YES;
370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
372 | CLANG_WARN_EMPTY_BODY = YES;
373 | CLANG_WARN_ENUM_CONVERSION = YES;
374 | CLANG_WARN_INFINITE_RECURSION = YES;
375 | CLANG_WARN_INT_CONVERSION = YES;
376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
381 | CLANG_WARN_STRICT_PROTOTYPES = YES;
382 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
383 | CLANG_WARN_UNREACHABLE_CODE = YES;
384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
386 | COPY_PHASE_STRIP = NO;
387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
388 | ENABLE_NS_ASSERTIONS = NO;
389 | ENABLE_STRICT_OBJC_MSGSEND = YES;
390 | GCC_C_LANGUAGE_STANDARD = gnu99;
391 | GCC_NO_COMMON_BLOCKS = YES;
392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
394 | GCC_WARN_UNDECLARED_SELECTOR = YES;
395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
396 | GCC_WARN_UNUSED_FUNCTION = YES;
397 | GCC_WARN_UNUSED_VARIABLE = YES;
398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
399 | MTL_ENABLE_DEBUG_INFO = NO;
400 | SDKROOT = iphoneos;
401 | SUPPORTED_PLATFORMS = iphoneos;
402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
403 | TARGETED_DEVICE_FAMILY = "1,2";
404 | VALIDATE_PRODUCT = YES;
405 | };
406 | name = Release;
407 | };
408 | 97C147061CF9000F007C117D /* Debug */ = {
409 | isa = XCBuildConfiguration;
410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
411 | buildSettings = {
412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
413 | CLANG_ENABLE_MODULES = YES;
414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
415 | ENABLE_BITCODE = NO;
416 | INFOPLIST_FILE = Runner/Info.plist;
417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
418 | PRODUCT_BUNDLE_IDENTIFIER = com.ymbok.example;
419 | PRODUCT_NAME = "$(TARGET_NAME)";
420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
422 | SWIFT_VERSION = 5.0;
423 | VERSIONING_SYSTEM = "apple-generic";
424 | };
425 | name = Debug;
426 | };
427 | 97C147071CF9000F007C117D /* Release */ = {
428 | isa = XCBuildConfiguration;
429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
430 | buildSettings = {
431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
432 | CLANG_ENABLE_MODULES = YES;
433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
434 | ENABLE_BITCODE = NO;
435 | INFOPLIST_FILE = Runner/Info.plist;
436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
437 | PRODUCT_BUNDLE_IDENTIFIER = com.ymbok.example;
438 | PRODUCT_NAME = "$(TARGET_NAME)";
439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
440 | SWIFT_VERSION = 5.0;
441 | VERSIONING_SYSTEM = "apple-generic";
442 | };
443 | name = Release;
444 | };
445 | /* End XCBuildConfiguration section */
446 |
447 | /* Begin XCConfigurationList section */
448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
449 | isa = XCConfigurationList;
450 | buildConfigurations = (
451 | 97C147031CF9000F007C117D /* Debug */,
452 | 97C147041CF9000F007C117D /* Release */,
453 | 249021D3217E4FDB00AE95B9 /* Profile */,
454 | );
455 | defaultConfigurationIsVisible = 0;
456 | defaultConfigurationName = Release;
457 | };
458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
459 | isa = XCConfigurationList;
460 | buildConfigurations = (
461 | 97C147061CF9000F007C117D /* Debug */,
462 | 97C147071CF9000F007C117D /* Release */,
463 | 249021D4217E4FDB00AE95B9 /* Profile */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | /* End XCConfigurationList section */
469 | };
470 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
471 | }
472 |
--------------------------------------------------------------------------------