{"id":32307293,"url":"https://github.com/spritewidget/spritewidget","last_synced_at":"2026-02-21T08:36:47.158Z","repository":{"id":21722053,"uuid":"93867017","full_name":"spritewidget/spritewidget","owner":"spritewidget","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-23T13:11:14.000Z","size":10667,"stargazers_count":1249,"open_issues_count":15,"forks_count":173,"subscribers_count":49,"default_branch":"master","last_synced_at":"2025-10-23T07:26:20.813Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spritewidget.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-09T14:29:01.000Z","updated_at":"2025-10-17T20:26:26.000Z","dependencies_parsed_at":"2022-08-09T19:40:10.786Z","dependency_job_id":null,"html_url":"https://github.com/spritewidget/spritewidget","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/spritewidget/spritewidget","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spritewidget%2Fspritewidget","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spritewidget%2Fspritewidget/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spritewidget%2Fspritewidget/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spritewidget%2Fspritewidget/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spritewidget","download_url":"https://codeload.github.com/spritewidget/spritewidget/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spritewidget%2Fspritewidget/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29677612,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T06:23:40.028Z","status":"ssl_error","status_checked_at":"2026-02-21T06:23:39.222Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-23T07:16:10.226Z","updated_at":"2026-02-21T08:36:47.144Z","avatar_url":"https://github.com/spritewidget.png","language":"Dart","readme":"# SpriteWidget\n\n[SpriteWidget](https://spritewidget.com) is a toolkit for building complex, high performance animations and 2D games with [Flutter](https://flutter.dev). Your sprite render tree lives inside a widget that mixes seamlessly with other Flutter and Material widgets. You can use SpriteWidget to create anything from an animated icon to a full fledged game. Checkout a [SpaceBlast](https://spritewidget.com/spaceblast) a complete [open source](https://github.com/spritewidget/spaceblast) game made with SpriteWidget!\n\n___SpriteWidget is sponsored by [Serverpod](https://serverpod.dev), the missing backend for Flutter. It's perfect if you need a backend for your game or any other app.___\n\nThis guide assumes a basic knowledge of Flutter and Dart. Get support by posting a question tagged `spritewidget` on [StackOverflow](https://stackoverflow.com/).\n\nYou can find examples in the [example](https://github.com/spritewidget/spritewidget/tree/master/example) directory, or check out the complete [Space Blast](https://github.com/spritewidget/spaceblast) game.\n\n![SpriteWidget](https://static1.squarespace.com/static/593b245d1e5b6ca18c9ffd52/t/5aa2b91324a6948406f5dee5/1520613684486/SpriteWidget?format=2500w)\n\n## Creating a SpriteWidget\n\nThe first thing you need to do to use SpriteWidget is to setup a root node that is used to draw it's contents. Any sprite nodes that you add to the root node will be rendered by the SpriteWidget. Typically, your root node is part of your app's state. This is an example of how you can setup a custom stateful widget with a SpriteWidget:\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:spritewidget/spritewidget.dart';\n\nclass MyWidget extends StatefulWidget {\n  @override\n  MyWidgetState createState() =\u003e new MyWidgetState();\n}\n\nclass MyWidgetState extends State\u003cMyWidget\u003e {\n  late NodeWithSize rootNode;\n\n  @override\n  void initState() {\n    super.initState();\n    rootNode = NodeWithSize(const Size(1024.0, 1024.0));\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SpriteWidget(rootNode);\n  }\n}\n```\n\nThe root node that you provide the SpriteWidget is a `NodeWithSize`, the size of the root node defines the coordinate system used by the SpriteWidget. By default the SpriteWidget uses letterboxing to display its contents. This means that the size that you give the root node will determine how the SpriteWidget's contents will be scaled to fit. If it doesn't fit perfectly in the area of the widget, either its top and bottom or the left and right side will be trimmed. You can optionally pass in a parameter to the SpriteWidget for other scaling options depending on your needs.\n\nWhen you have added the SpriteWidget to your app's build method it will automatically start running animations and handling user input. There is no need for any other extra setup.\n\n## Adding objects to your node graph\n\nYour SpriteWidget manages a node graph, the root node is the `NodeWithSize` that is passed in to the SpriteWidget when it's created. To render sprites, particles systems, or any other objects simply add them to the node graph.\n\nEach node in the node graph has a transform. The transform is inherited by its children, this makes it possible to build more complex structures by grouping objects together as children to a node and then manipulating the parent node. For example the following code creates a car sprite with two wheels attached to it. The car is added to the root node.\n\n```dart\nSprite car = Sprite.fromImage(carImage);\nSprite frontWheel = Sprite.fromImage(wheelImage);\nSprite rearWheel = Sprite.fromImage(wheelImage);\n\nfrontWheel.position = const Offset(100, 50);\nrearWheel.position = const Offset(-100, 50);\n\ncar.addChild(frontWheel);\ncar.addChild(rearWheel);\n\nrootNode.addChild(car);\n```\n\nYou can manipulate the transform by setting the position, rotation, scale, and skew properties.\n\n## Sprites, textures, and sprite sheets\n\nTo load image resources, the easiest way is to use the `ImageMap` class. The `ImageMap` can load one or multiple images at once.\n\nThe `Image` class isn't automatically imported through flutter/material, so you may need to add an import at the top of your file.\n\n```dart\nimport 'dart:ui' as ui;\n```\n\n Now you can load images using the `ImageMap`. Note that the loading methods are asynchronous, so this example code will need to go in an asynch method. For a full example of loading images see the [Weather Demo](https://github.com/spritewidget/spritewidget/tree/master/examples/weather).\n\n```dart\nImageMap images = ImageMap();\n\n// Load a single image\nvar myImage = await images.loadImage('assets/my_image.png');\n\n// Load multiple images\nawait images.load([\n  'assets/image_0.png',\n  'assets/image_1.png',\n  'assets/image_2.png',\n]);\n\n// Access a loaded image from the ImageMap\nvar image0 = images['assets/image_0.png'];\n```\n\nThe most common node type is the `Sprite` node. A sprite simply draws an image to the screen. Sprites can be drawn from Image objects or SpriteTexture objects. A texture is a part of an Image. Using a SpriteSheet you can pack several texture elements within a single image. This saves space in the device's gpu memory and also make drawing faster. Currently SpriteWidget supports sprite sheets in json format and produced with a tool such as TexturePacker. It's uncommon to manually edit the sprite sheet files. You can create a SpriteSheet with a definition in json and an image:\n\n```dart\nSpriteSheet sprites = SpriteSheet(\n  image: myImage,\n  jsonDefinition: jsonCode,\n);\nSpriteTexture texture = sprites['texture.png'];\n```\n\n## The frame cycle\n\nEach time a new frame is rendered to screen SpriteWidget will perform a number of actions. Sometimes when creating more advanced interactive animations or games, the order in which these actions are performed may matter.\n\nThis is the order things will happen:\n\n1. Handle input events\n2. Run animation motions\n3. Call update functions on nodes\n4. Apply constraints\n5. Render the frame to screen\n\nRead more about each of the different phases below.\n\n## Handling user input\n\nYou can subclass any node type to handle touches. To receive touches, you need to set the userInteractionEnabled property to true and override the handleEvent method. If the node you are subclassing doesn't have a size, you will also need to override the isPointInside method.\n\n```dart\nclass EventHandlingNode extends NodeWithSize {\n  EventHandlingNode(Size size) : super(size) {\n    userInteractionEnabled = true;\n  }\n\n  @override handleEvent(SpriteBoxEvent event) {\n    if (event.type == PointerEventType.down)\n      ...\n    else if (event.type == PointerEventType.move)\n      ...\n\n    return true;\n  }\n}\n```\n\nIf you want your node to receive multiple touches, set the `handleMultiplePointers` property to true. Each touch down or dragged touch will generate a separate call to the handleEvent method, you can distinguish each touch by its pointer property.\n\n## Animating using motions\n\nSpriteWidget provides easy to use functions for animating nodes through motions. You can combine simple motion blocks to create more complex animations.\n\nTo execute a motion animation you first build the motion itself, then pass it to the run method of a nodes motion manager (see the Tweens section below for an example).\n\n### Tweens\n\nTweens are the simplest building block for creating an animation. It will interpolate a value or property over a specified time period. You provide the `MotionTween` class with a setter function, its start and end value, and the duration for the tween.\n\nAfter creating a tween, execute it by running it through a node's motion manager.\n\n```dart\nvar myNode = Node();\n\nvar myTween = MotionTween\u003cOffset\u003e (\n  setter: (a) =\u003e myNode.position = a,\n  start: Offset.zero,\n  end: const Offset(100.0, 0.0),\n  duration: 1.0,\n);\n\nmyNode.motions.run(myTween);\n```\n\nYou can animate values of different types, such as floats, points, rectangles, and even colors. You can also optionally provide the MotionTween class with an easing function.\n\n### Sequences\n\nWhen you need to play two or more motions in a sequence, use the MotionSequence class:\n\n```dart\nvar sequence = MotionSequence(\n  motions: [\n    firstMotion,\n    middleMotion,\n    lastMotion,\n  ],\n);\n```\n\n### Groups\n\nUse MotionGroup to play motions in parallel:\n\n```dart\nvar group = MotionGroup(\n  motions: [\n    motion0,\n    motion1,\n  ],\n);\n```\n\n### Repeat\n\nYou can loop any motion, either a fixed number of times, or until the end of times:\n\n```dart\nvar repeat = MotionRepeat(\n  motion: loopedMotion,\n  numRepeats: 5,\n);\n\nvar longLoop = MotionRepeatForever(motion: loopedMotion);\n```\n\n### Composition\n\nIt's possible to create more complex motions by composing them in any way:\n\n```dart\nvar complexMotion = MotionSequence(\n  motions: [\n    MotionRepeat(motion: myLoop, numRepeats: 2), \n    MotionGroup(\n      motions: [\n        motion0,\n        motion1,\n      ],\n    ),\n  ],\n);\n```\n\n## Handle update events\n\nEach frame, update events are sent to each node in the current node tree. Override the update method to manually do animations or to perform game logic.\n\n```dart\nMyNode extends Node {\n  @override\n  update(double dt) {\n    // Move the node at a constant speed\n    position += Offset(dt * 1.0, 0.0);\n  }\n}\n```\n\n## Defining constraints\n\nConstraints are used to constrain properties of nodes. They can be used to position nodes relative other nodes, or adjust the rotation or scale. You can apply more than one constraint to a single node.\n\nFor example, you can use a constraint to make a node follow another node at a specific distance with a specified dampening. The dampening will smoothen out the following node's movement.\n\n```dart\nfollowingNode.constraints = [\n  ConstraintPositionToNode(\n    targetNode: targetNode,\n    offset: const Offset(0.0, 100.0),\n    dampening: 0.5,\n  ),\n];\n```\n\nConstraints are applied at the end of the frame cycle. If you need them to be applied at any other time, you can directly call the `applyConstraints` method of a `Node` object.\n\n## Perform custom drawing\n\nSpriteWidget provides a default set of drawing primitives, but there are cases where you may want to perform custom drawing. To do this you will need to subclass either the `Node` or `NodeWithSize` class and override the paint method:\n```dart\nclass RedCircle extends Node {\n  RedCircle(this.radius);\n\n  double radius;\n\n  @override\n  void paint(Canvas canvas) {\n    canvas.drawCircle(\n      Offset.zero,\n      radius,\n      new Paint()..color = const Color(0xffff0000),\n    );\n  }\n}\n```\n\nIf you are overriding a `NodeWithSize` you may want to call `applyTransformForPivot` before starting drawing to account for the node's pivot point. After the call the coordinate system is setup so you can perform drawing starting at origo to the size of the node.\n\n```dart\n@override\nvoid paint(Canvas canvas) {\n  applyTransformForPivot(canvas);\n\n  canvas.drawRect(\n    new Rect.fromLTWH(0.0, 0.0, size.width, size.height),\n    myPaint\n  );\n}\n```\n\n## Add effects using particle systems\n\nParticle systems are great for creating effects such as rain, smoke, or fire. It's easy to setup a particle system, but there are very many properties that can be tweaked. The best way of to get a feel for how they work is to simply play around with them.\n\nIf you want to play around with particle systems, try out the [Particle Designer](https://spritewidget.com/particledesigner) (web app).\n\nThis is an example of how a particle system can be created, configured, and added to the scene:\n\n```dart\nParticleSystem particles = ParticleSystem(\n  texture: particleTexture,\n  posVar: const Point(100, 100.0),\n  startSize: 1.0,\n  startSizeVar: 0.5,\n  endSize: 2.0,\n  endSizeVar: 1.0,\n  life: 1.5 * distance,\n  lifeVar: 1.0 * distance\n);\n\nrootNode.addChild(particles);\n```\n","funding_links":[],"categories":["引擎","Game [🔝](#readme)","Frameworks"],"sub_categories":["游戏引擎","游戏","Game"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspritewidget%2Fspritewidget","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspritewidget%2Fspritewidget","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspritewidget%2Fspritewidget/lists"}