Lab 056 – Using Emoji in Particle Emitters

We can render emoji as Texture Resources to be used in our Particle Emitters.

Overview

Building on what we saw in Lab 055, I was wondering if we could render emoji too. We can, but with a notable caveat.

Let’s take a look at generateTextureFromEmoji. We create an attributed string and place it in the center, then render it as an image.

// Adapted from generateTextureFromSystemName in the example project called "Simulating particles in your visionOS app"
// Sources: https://developer.apple.com/documentation/realitykit/simulating-particles-in-your-visionos-app
    func generateTextureFromEmoji(_ emoji: String) -> TextureResource? {
        // Create aa image from an emoji
        let imageSize = CGSize(width: 128, height: 128)

        // Start the graphics context
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)

        // Create an attributed string with the emoji
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 96)  // Large size to fill the texture
        ]
        let attributedString = NSAttributedString(string: emoji, attributes: attributes)

        // Calculate the size of the emoji
        let stringSize = attributedString.size()

        // Calculate the position to center the emoji
        let x = (imageSize.width - stringSize.width) / 2
        let y = (imageSize.height - stringSize.height) / 2

        // Draw the emoji
        attributedString.draw(at: CGPoint(x: x, y: y))

        // Get the image from the context
        let contextImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // Convert to texture resource
        guard let coreGraphicsImage = contextImage?.cgImage else {
            return nil
        }

        let creationOptions = TextureResource.CreateOptions(semantic: .raw)
        return try? TextureResource(image: coreGraphicsImage, options: creationOptions)
    }

The caveat I mentioned is on ParticleEmitterComponent. So far, I have not found a way to create a particle that does not have a color. Passing in .clear will create transparent particles. It seems like we have to provide at least one color to the particle. The best option I found was to use .white. This will apply a slight tint to the emoji, but they still recognizable. Passing any other color tinted them too much. Let me know if you have any ideas for how to solve this.

I have no idea if Apple will approve an app that uses emoji like this. I know there are been instances of them rejecting apps for using emoji outside of their intended use case.

Full Lab Code

struct Lab056: View {

    @State private var selectedEmojo: String = "❤️"

    let emoji = ["❤️", "🎉",  "🏳️‍🌈", "🐸", "🚀", "🌸"]

    var body: some View {
        RealityView { content in

            let subject = Entity()
            subject.name = "Particles"
            content.add(subject)

            // Create the particle emitter
            var particleSystem = ParticleEmitterComponent()
            particleSystem.isEmitting = true
            particleSystem.speed = 0.01
            particleSystem.emitterShape = .sphere
            particleSystem.emitterShapeSize = [0.3, 0.3, 0.3]

            // Set the image to the result of generateTextureFromSystemName using a symbol name
            particleSystem.mainEmitter.image = generateTextureFromEmoji(selectedEmojo)
            particleSystem.mainEmitter.birthRate = 25
            particleSystem.mainEmitter.size = 0.1

            // Issues: I haven't found a way to specify no color for the particles.
            // The best option I found is to provide a white constant color. This will tint the emoji a bit.
            particleSystem.mainEmitter.color = .constant(.single(.white))

            // Add the component to the entity
            subject.components.set(particleSystem)

        } update: { content in

            if let subject = content.entities.first?.findEntity(named: "Particles") {
                if var particleSystem = subject.components[ParticleEmitterComponent.self] {
                    particleSystem.mainEmitter.image = generateTextureFromEmoji(selectedEmojo)
                    subject.components.set(particleSystem)
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .bottomOrnament, content: {
                HStack {
                    ForEach(emoji, id: \.self) { symbol in
                        Text(symbol)
                            .frame(width: 32, height: 32)
                            .foregroundColor(.white)
                            .onTapGesture {
                                selectedEmojo = symbol
                            }
                            .padding(6)
                            .background( selectedEmojo == symbol ? .white : Color.clear)
                            .clipShape(.circle)
                    }
                }
            })
        }
    }

    // Adapted from generateTextureFromSystemName in the example project called "Simulating particles in your visionOS app"
    // Sources: https://developer.apple.com/documentation/realitykit/simulating-particles-in-your-visionos-app
    func generateTextureFromEmoji(_ emoji: String) -> TextureResource? {
        // Create aa image from an emoji
        let imageSize = CGSize(width: 128, height: 128)

        // Start the graphics context
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)

        // Create an attributed string with the emoji
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 96)  // Large size to fill the texture
        ]
        let attributedString = NSAttributedString(string: emoji, attributes: attributes)

        // Calculate the size of the emoji
        let stringSize = attributedString.size()

        // Calculate the position to center the emoji
        let x = (imageSize.width - stringSize.width) / 2
        let y = (imageSize.height - stringSize.height) / 2

        // Draw the emoji
        attributedString.draw(at: CGPoint(x: x, y: y))

        // Get the image from the context
        let contextImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // Convert to texture resource
        guard let coreGraphicsImage = contextImage?.cgImage else {
            return nil
        }

        let creationOptions = TextureResource.CreateOptions(semantic: .raw)
        return try? TextureResource(image: coreGraphicsImage, options: creationOptions)
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more labs from Step Into Vision.

Questions or feedback?